Why React
React is an open-source JavaScript library — not a full framework — for building interactive UIs out of small, reusable pieces called components.
- Component-based — the UI is a tree of independent, composable pieces
- Declarative — you describe what the UI should look like for a given state, and React figures out how to update the DOM
- JSX — an HTML-like syntax extension that compiles down to plain JavaScript function calls
- One-way data flow — data moves down from parent to child through props, which keeps state predictable
- Hooks — let function components manage state and side effects without ever writing a class
Components, props, and state
function Greeting({ name }) { // props are read-only inputs
const [count, setCount] = useState(0); // state can change over time
return (
<div>
<p>Hello, {name}!</p>
<button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
</div>
);
}
- Props are passed down from a parent and are read-only — a child can never modify its own props.
- State belongs to a component and changing it (via its setter) triggers a re-render.
- Local state lives in one component; shared state is lifted up to a common parent (or a store) so multiple components can coordinate.
Hooks and the component lifecycle
Function components don't have lifecycle methods, but hooks cover the same needs:
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(setData);
return () => controller.abort(); // cleanup on unmount
}, []); // empty array -> run once, on mount
- Mounting — the component initializes, sets up state, and runs any one-time side effects
- Updating — a state or prop change re-runs the function body and re-evaluates the JSX
- Unmounting — cleanup functions returned from
useEffectrun here (cancel subscriptions, timers, requests)
useState manages local state, useEffect handles side effects, and useReducer steps in when state transitions get complex enough that a single setState call isn't enough.
Project structure and styling
Modern React projects are commonly scaffolded with Vite, which is faster than the older Create React App because it serves source files directly over native ES modules instead of bundling everything up front:
npm create vite@latest my-app -- --template react
my-app/
├─ public/ # static assets
├─ src/
│ ├─ main.jsx # entry point
│ └─ App.jsx # root component
├─ package.json
└─ vite.config.js
Styling options range from simple to structured:
- Inline styles — a JS object passed to
style={{ ... }} - CSS Modules —
Component.module.css, scoped automatically to that component - Styled-components / CSS-in-JS — styles defined alongside the component, with full access to props
Testing and app-wide state
Modern React tests focus on user-visible behavior, not implementation details — because concurrent rendering means updates aren't always synchronous:
test('increments the counter on click', async () => {
render(<Counter />);
await userEvent.click(screen.getByRole('button'));
expect(await screen.findByText('Clicked 1 times')).toBeInTheDocument();
});
- Prefer
findBy*queries andwaitForover asserting on the DOM immediately after an interaction. - Simulate real interactions (clicks, typing, network responses) rather than calling internal methods directly.
For state shared across many components, Redux Toolkit cuts the boilerplate of classic Redux:
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: { increment: (state) => { state.value += 1 } },
});
const store = configureStore({ reducer: counterSlice.reducer });
Tip:
createSlice()lets you "mutate" state directly inside a reducer — Redux Toolkit uses Immer under the hood to turn that into a safe, immutable update.
