Module 2: JSX and Components
Function Components and the Rules of Rendering
How React calls your components, why they have to be pure, and the mistakes that come from misunderstanding what a component actually is.
A function component is a JavaScript function that returns something React can render, usually JSX. That's the definition. All of the complexity people associate with React comes from what happens around that function: who calls it, how often, and with what.
function Greeting({ name }) {
return <h2>Hello, {name}</h2>;
}
You never call Greeting() yourself. You put <Greeting name="Asha" /> in JSX, and React decides when to call it.
Who calls your function, and when#
React calls a component in three situations: when it first appears on the screen, when its own state changes, and when its parent renders (unless you've told React to skip that, which we'll cover in the performance lesson).
Each call is a completely fresh run. Local variables are brand new. Functions defined inside are brand new. The only things that carry over are the things React stores for you, like state.
function Demo() {
const startedAt = Date.now(); // different on every render
const [count, setCount] = useState(0); // remembered by React
console.log("rendering", count);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Click that button and you'll see "rendering 1", "rendering 2" and so on. React is calling Demo again each time and handing it the latest count.
Components form a tree#
Components call other components (by returning them in JSX), and that makes a tree. React works through the tree top to bottom during a render.
App
/ \
Navbar Main
│ / \
Logo Sidebar ProductList
│
ProductCard x N
When ProductList renders, it produces elements for each ProductCard, and React then calls each of those. A parent always renders before its children. That ordering is worth remembering when you debug a flood of logs.
Purity: the one rule that matters#
During rendering, a component should behave like a math function. Same inputs (props, state, context), same output. No surprises.
That means render shouldn't:
- change variables that exist outside the component
- mutate props or state
- send network requests, start timers, or touch the DOM
- depend on randomness or the current time for its output, unless you accept it changes every render
// Impure: changes something outside the component
let guestCount = 0;
function Guest() {
guestCount = guestCount + 1;
return <p>Guest #{guestCount}</p>;
}
Render this component three times and you get three different results from the same code. In StrictMode, React deliberately renders twice in development to expose exactly this kind of mistake. If your numbers jump by two, that's React telling you something's impure.
The fix is to pass what you need in:
function Guest({ guestNumber }) {
return <p>Guest #{guestNumber}</p>;
}
Side effects (fetching, subscribing, writing to localStorage) are not forbidden. They just don't belong in the render body. They go in event handlers, or in useEffect for things that must happen because the component is on screen. Module 4 covers that.
A small exception that's fine: creating and modifying variables that live entirely inside the component during one render.
function TagList({ tags }) {
const items = []; // created fresh in this render
for (const t of tags) {
items.push(<li key={t}>{t}</li>);
}
return <ul>{items}</ul>; // local mutation, nobody else sees it
}
Never define a component inside another component#
This one causes confusing bugs, so it gets its own heading.
function Page() {
const [text, setText] = useState("");
function Input() { // a NEW function on every Page render
return <input value={text} onChange={(e) => setText(e.target.value)} />;
}
return <Input />;
}
React tells components apart by their identity. Every time Page renders, Input is a brand new function, so React treats it as a different component type than the last render. It throws away the old one, including its DOM node and focus, and mounts a fresh one. You'll type one character and the input loses focus.
Define components at the top level of the module. If an inner component needs data from the outer one, pass it as props.
Naming and files#
- Component names start with a capital letter. That's how JSX tells components from HTML tags.
- One main component per file, named after the file, is a good default. Tiny helper components used only in one place can share the file.
- Keep the return value simple. If JSX gets deeply nested, that's a signal to split off a component.
A note about refs in React 19#
In current React, a function component receives ref as a normal prop, so you can pass it down without extra wrapping. The old forwardRef helper still works but you no longer need it in new code. We'll use this in the refs lesson.
Where people get burned#
- Defining components inside components, then losing input focus or state on every keystroke.
- Mutating a prop or a state object inside render. It might even appear to work, until a later render shows stale data.
- Doing a
fetchstraight in the function body. That runs on every render and can trigger an infinite loop of re-renders. - Naming a component in lowercase and wondering why React renders an unknown HTML element.
- Putting too many responsibilities in one component, then being scared to edit it.
Practice#
- Write a
Counterthat logs every time it renders. Click the button a few times and predict the log before each click. - Create the nested
Inputexample above and reproduce the lost-focus bug. Then moveInputoutside and confirm it's fixed. - Take the impure
Guestexample and turn it into a pure version used by aGuestListthat renders five guests. - Build a small tree with
App,HeaderandFooter, add aconsole.login each, and write down the order they print in.