Back to React
React
easy
junior

What is the difference between props and state in React?

Props are inputs passed in from a parent — read-only inside the component. State is internal, mutable via `setState`. Props flow down; events flow up. If multiple components need the same value, lift it to the lowest common parent and pass it down as a prop. If a piece of data can be derived from props, don't put it in state.

4 min read·~8 min to think through

Two ways data lives in a React component.

Props. Input from the parent. Read-only.

tsx
function Avatar({ user, size }: { user: User; size: number }) {
  // user and size are props — set by the caller, can't be reassigned here
  return <img src={user.avatarUrl} width={size} />;
}

State. Internal, owned by the component, mutated via a setter.

tsx
function Counter() {
  const [count, setCount] = useState(0); // state
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Data-flow rules.

  1. Props flow down. Parent → child, never the reverse.
  2. Events flow up. Children call callbacks passed down as props; the parent decides what to do with them.
  3. State lives at the lowest common ancestor of the components that need it. This is called lifting state.
tsx
// Two siblings need the same temperature value.
function App() {
  const [temp, setTemp] = useState(0);
  return (
    <>
      <Slider value={temp} onChange={setTemp} />
      <Display value={temp} />
    </>
  );
}

temp lives in the parent because both children need it. The slider takes it as a prop and reports changes via a callback prop.

The two rules that matter most.

  1. Don't put derived data in state.

```tsx // Bad — derived state. fullName drifts out of sync. const [first, setFirst] = useState(""); const [last, setLast] = useState(""); const [fullName, setFullName] = useState(""); // ← don't

// Good — compute on render. const fullName = ${first} ${last}; ```

If you can compute it, do — every render is cheap, and you eliminate a class of "I forgot to update X when Y changed" bugs.

  1. Don't copy props into state without a reason.

``tsx // Usually wrong function Field({ value }) { const [internal, setInternal] = useState(value); ... } ``

The component now ignores future changes to value from the parent. Use it directly. Copy into state only when you genuinely want to fork the value (e.g., a draft for editing that the user can cancel).

When you DO want to mirror a prop into state.

The "uncontrolled-with-controlled-reset" pattern:

tsx
<Editor key={item.id} initialValue={item.text} />

Inside, useState(initialValue) is fine, because key={item.id} forces a remount when the parent's selected item changes — so internal state always starts from the right prop.

Props can be functions, objects, JSX. Anything. Children-as-prop is common:

tsx
<Card title="Hi">
  <p>body</p>
</Card>
// Inside Card, you receive a `children` prop with the <p> element.

Context bypasses the prop chain — but it's not state. It's a way to deliver a value (which itself is state somewhere up the tree) to deeply nested consumers without manual prop drilling.

Senior framing. The mental model is: state is the minimum amount of mutable input the component needs to remember between renders. Everything else is either a prop (passed in) or a derived value (computed during render). Most "why is my component buggy" questions trace back to having too much state — variables that should have been derived.

Follow-up questions

  • When should you lift state vs use context?
  • Why is derived state a common bug source?
  • When is it correct to mirror a prop into state?
  • What does 'data flows down, events flow up' mean in practice?

Common mistakes

  • Putting derived values (sum, fullName, filtered list) in state.
  • Copying props into state unnecessarily, so prop updates are ignored.
  • Mutating a prop or an object inside state directly.
  • Adding state in too many places instead of lifting once.

Performance considerations

  • Each setState triggers a re-render of the owning component and its subtree.
  • Splitting state into multiple useState calls is fine; React batches updates in event handlers.

Edge cases

  • Async setState calls inside async functions in React 17 don't auto-batch (fixed in 18).
  • Updating state during render causes a loop unless guarded.
  • Storing functions in state — useState lazily calls them; pass via setter callback instead.

Real-world examples

  • Form input value (controlled) — state. Form schema — prop. Theme — context (state higher up).

Related questions