Skip to content

State in React

State is data that changes over time and affects what the user sees. Unlike props, state is owned and managed inside a component (or lifted to a shared ancestor). This lesson covers state basics before diving into the useState hook in detail.

What Is State?

State represents anything that can change: form input values, open/closed toggles, fetched data, selected tabs. When state updates, React re-renders the component and updates the DOM to match.

Each component instance has its own state. Two <Counter /> components on the same page maintain independent counts unless you lift state to a shared parent.

function Toggle() {
  const [isOn, setIsOn] = useState(false);

  return (
    <button onClick={() => setIsOn(prev => !prev)}>
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}

Calling setIsOn schedules a re-render with the new state value.

State Update Rules

// Functional update (safe when next state depends on previous)
setCount(c => c + 1);

// Object state — spread previous values
setUser(u => ({ ...u, name: 'Ada' }));

// Never mutate directly
state.items.push(x);  // WRONG
setItems([...items, x]);  // RIGHT
  • State updates are asynchronous and may be batched in React 18+.
  • Use functional updates when the new state depends on the previous state.
  • Treat state as immutable — create new objects/arrays instead of mutating.
  • Lift state to the closest common ancestor when siblings need to share it.

State vs Props

Quick comparison of React's two data inputs.

Props State
Owned by Parent Component (or lifted parent)
Mutable by child? No Yes (via setter)
Triggers re-render? When parent re-renders When setter is called
Default source Parent passes down useState, useReducer, etc.
Use for Configuration, callbacks Interactive, changing data

Lifting State Up

When two components need the same data, move state to their closest common parent. Pass state down as props and pass setters or callbacks so children can request updates.

function Parent() {
  const [value, setValue] = useState('');
  return (
    <>
      <Input value={value} onChange={setValue} />
      <Preview text={value} />
    </>
  );
}

Both Input and Preview stay in sync because they share one source of truth.

Derived State and Computed Values

Avoid storing data in state that you can compute from other state or props. Derive values during render or memoize with useMemo when computation is expensive.

  • Bad: storing fullName when you have firstName and lastName.
  • Good: const fullName = firstName + ' ' + lastName during render.
  • Use useMemo only when profiling shows a real performance cost.
  • Duplicated state leads to sync bugs when one copy updates without the other.

Common Mistakes

  • Mutating state objects or arrays directly.
  • Storing redundant derived data in state.
  • Reading state immediately after setState expecting it to be updated.
  • Putting everything in global state when local state suffices.

Key Takeaways

  • State is mutable data that triggers re-renders when updated.
  • Updates must be immutable; use setters from useState or useReducer.
  • Lift state up when multiple components need the same data.
  • Derive values from state instead of duplicating them.

Pro Tip

Start with local state. Only lift or globalize when a concrete sharing need appears — premature global state adds complexity.