ESC

Type to search the knowledge base.

Props vs State

Props are inputs from the parent; state is data the component owns over time. How to choose, lift, and avoid the usual traps.

beginner5 min read
  • react
  • props
  • state

Every React UI is a function of props and state (plus context). Mixing them up produces the classic bugs: “I updated the prop and nothing happened,” “two sources of truth,” and “why does typing reset when the parent re-renders?”

Props State
Who owns it? Parent (or caller of the component) The component that declared useState / useReducer
Can the child change it? No — props are read-only inputs Yes — via the setter
When does it change? Parent re-renders with new values After a state update is processed
Mental model Function arguments Memory over time for this instance

Docs: Passing Props, State: A Component’s Memory.

Props: the public API of a component

function Avatar({ src, alt, size = 40 }) {
  return (
    <img
      src={src}
      alt={alt}
      width={size}
      height={size}
      className="rounded-full"
    />
  );
}

// Parent decides values
<Avatar src={user.avatarUrl} alt={user.name} size={48} />

Props make a component reusable. The same Avatar renders different people without knowing where the data lives.

Conventions that scale:

  • Treat props as immutable from the child’s point of view. Don’t mutate props.user.name = ….
  • Prefer explicit props over a giant config bag until you have a real pattern.
  • Use children for composition slots:
function Panel({ title, children }) {
  return (
    <section>
      <h2>{title}</h2>
      {children}
    </section>
  );
}

State: memory for one component instance

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button type="button" onClick={() => setCount((c) => c + 1)}>
      Clicked {count}
    </button>
  );
}

State updates are asynchronous from your perspective (scheduled). Reading count immediately after setCount still shows the old value in the same event handler — use the next render or a functional update when the new value depends on the previous one.

State is tied to the component identity in the tree (type + position + key). Unmount and remount → state resets. Change key intentionally when you want a clean slate (e.g. key={userId} on a form).

Choosing: does this component own the data?

Ask:

  1. Can the parent compute it? → prop or derive during render
  2. Does only this component care over time (open/closed, draft text)? → local state
  3. Do siblings need the same data? → lift state to the closest common parent
  4. Does half the app need it? → context or external store — not every global is “context”

Controlled vs uncontrolled inputs

Controlled: parent/state is the source of truth for the value.

function Search({ value, onChange }) {
  return (
    <input
      value={value}
      onChange={(e) => onChange(e.target.value)}
      aria-label="Search"
    />
  );
}

Uncontrolled: the DOM holds the value; you read via ref when needed.

function FilePicker() {
  const inputRef = useRef(null);
  function onSubmit() {
    const file = inputRef.current?.files?.[0];
    // ...
  }
  return <input ref={inputRef} type="file" />;
}

Pick one model per field. The hybrid “state mirrors props without a reset strategy” is how forms desync.

Lifting state (and putting it back down)

function FilterableList({ items }) {
  const [query, setQuery] = useState('');
  const visible = items.filter((item) =>
    item.name.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <>
      <Search value={query} onChange={setQuery} />
      <ul>
        {visible.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </>
  );
}

query lives in the parent because both the input and the list need it. Search stays dumb and testable.

Anti-pattern: duplicate state in parent and child that can diverge.

// ❌ child copies prop into state once and never follows updates
function Bad({ color }) {
  const [c, setC] = useState(color);
  return <div style={{ background: c }} />;
}

If the prop is the source of truth, use the prop. If you need a draft that starts from a prop, document the reset rule (key, or sync intentionally when an id changes).

Derived values are not state

// ❌
const [total, setTotal] = useState(0);
useEffect(() => {
  setTotal(items.reduce((s, i) => s + i.price, 0));
}, [items]);

// ✅
const total = items.reduce((s, i) => s + i.price, 0);

Extra state for pure calculations causes extra renders and stale windows. Same idea as in useEffect Fundamentals: don’t sync what you can compute.

Props drilling vs context

Drilling 2–3 levels is fine. When every intermediate component only forwards props, consider context for stable ambient data (theme, locale, auth user) — not for high-frequency values like mouse position unless you’ve measured and designed for it.

Interview angle

Q: Difference between props and state?
A: Props are external inputs controlled by the parent; state is internal memory. Children should not mutate props. Updates to either can trigger re-render.

Q: When do you lift state?
A: When multiple children need the same changing data, or the parent must coordinate.

Q: Controlled component?
A: Form element whose value is driven by React state/props every render, with onChange updating that state.

Bonus: explain that re-render ≠ DOM update (Reconciliation) and that identity/key affects whether state is preserved.

Further reading

Related guides