#State vs props
Props are what a component is given; state is what a component remembers. Props come from the parent and the component can't change them. State belongs to the component instance, survives between renders, and changes when the component calls its setter — and every change makes React render that component again.
From my notes: props are inputs passed from a parent, immutable, and make a component dynamic; state is local to a component, can change over time, and controls its behaviour. One correction: my notes call state mutable. It changes over time, but you never mutate it — you replace it by calling the setter with a new value, and React keeps the old value untouched until the next render. Treat both props and state as read-only snapshots; the difference is who's allowed to ask for a new one.
A mental model: props are the settings someone else chose for you (like the temperature a thermostat is told to hold); state is your own memory (like how long the heater has been on). A parent can change your settings; only you can change your memory.
import { useState } from "react";
export function StepCounter({ step, label }) {
const [count, setCount] = useState(0); // owned here, changes over time
return (
<button onClick={() => setCount(count + step)}>
{label}: {count}
</button>
);
}
export function Settings() {
const [step, setStep] = useState(1);
return (
<>
<StepCounter step={step} label="Score" />
<button onClick={() => setStep(10)}>Use step 10</button>
</>
);
}
// click Score twice → "Score: 2"
// click "Use step 10" → still "Score: 2" (the prop changed, the state didn't)
// click Score once more → "Score: 12"Settingsownsstep(state, starts at1) and passes it toStepCounteras a prop.StepCounterownscount(its own state, starts at0).labelis a prop too, a fixed string.- Two clicks on Score: each runs
setCount(count + step)withstep = 1, socountgoes0 → 1 → 2and the button readsScore: 2. OnlyStepCounterre-renders;Settingsis untouched. - "Use step 10" changes
Settings's state.Settingsre-renders and passesstep={10}down.StepCounterre-renders with the new prop — but itscountstays2, because state belongs to the instance and isn't reset by new props. - The next Score click uses the new prop:
2 + 10 = 12. Props and state combine freely during render; what differs is ownership. StepCountercan't changestepitself. If it needed to,Settingswould pass down a callback likeonStepChange(see One-way data flow).
| Props | State | |
|---|---|---|
| Where it comes from | The parent, as JSX attributes | The component itself (useState, useReducer) |
| Who can change it | Only the parent, by re-rendering with new values | Only the component, by calling its setter |
| Changes trigger a re-render? | Yes, when the parent re-renders | Yes, when the setter gets a different value |
| Survives re-renders | Re-supplied by the parent each time | Kept by React for that instance |
| Reset when | Parent passes something else | Component unmounts, or its key changes |
| Mutate it directly? | No — frozen in development | No — replace it via the setter |
For a product page, which of these should be state in the component? (a) the product id from the URL, (b) whether the "details" panel is expanded, (c) the price including tax, (d) the text in a review textarea.
Show answer
Only (b) and (d).
- (a) comes from outside (the router or the parent), so it's a prop or comes from a hook like
useParams— not something this component decides. - (b) changes over time because of the user, and nothing else can compute it, so it's state.
- (c) can be computed during render from the price and tax rate. Storing it in state means keeping two copies in sync, which is how stale-total bugs appear (see Where state lives: a decision guide).
- (d) changes as the user types and must survive re-renders, so it's state (for a controlled textarea), or it can live in the DOM (uncontrolled). Either way, not a prop.