ESC

Type to search the knowledge base.

useState Patterns

Practical useState patterns: functional updates, objects versus atoms, lazy init, immutable lists, and when to use useReducer.

beginner3 min read
  • react
  • usestate-patterns

useState is the default state hook: a value for this render and a setter that schedules an update. Most production bugs around it are stale closures, overwritten objects, or state that should not exist.

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

Docs: useState, State: A Component’s Memory.

Functional updates

When the next value depends on the previous, use the updater form:

function Counter() {
  const [n, setN] = useState(0);
  return (
    <button
      type="button"
      onClick={() => {
        setN((c) => c + 1);
        setN((c) => c + 1); // +2 after batch
      }}
    >
      {n}
    </button>
  );
}

setN(n + 1) twice in one click uses the same n from the render that created the handler, so you only get +1. See batching.

Multiple atoms versus one object

// Separate atoms when fields update independently
const [name, setName] = useState('');
const [email, setEmail] = useState('');

// One object when fields are a single snapshot
const [form, setForm] = useState({ name: '', email: '' });
setForm((f) => ({ ...f, email: next }));

Hooks replace state. They do not shallow-merge objects like class setState. Forgetting the spread wipes sibling fields.

Lazy initialization

Expensive initial state should be a function so it runs once on mount:

const [table, setTable] = useState(() => buildHugeIndex(rawData));

useState(buildHugeIndex(rawData)) calls buildHugeIndex every render even though only the first result is kept.

Toggle and list patterns

setOn((v) => !v);

setItems((items) => items.concat(newItem));
setItems((items) => items.filter((i) => i.id !== id));
setItems((items) =>
  items.map((i) => (i.id === id ? { ...i, done: true } : i))
);

Prefer immutable updates so React’s Object.is comparison sees a new reference when the UI should update, and you avoid shared mutation bugs.

When useReducer is clearer

Complex transitions, multi-field updates that must stay consistent, or event-to-state tables:

function reducer(state, action) {
  switch (action.type) {
    case 'type':
      return { ...state, text: action.text };
    case 'submit':
      return { ...state, status: 'saving' };
    case 'success':
      return { ...state, status: 'saved', text: '' };
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(reducer, { text: '', status: 'idle' });

Same scheduling model as useState; different organization for branching logic.

What not to store

Do not put the entire server cache in a pile of useState if you mean a shared store — use a data library. Do not store values you can derive. Do not store DOM nodes in state when a ref is enough.

Interview out-loud

“useState holds render state and queues updates. I use functional updaters when depending on previous state, spread when updating objects, lazy init for expensive defaults, and useReducer when transitions get branching. I keep state minimal and compute derived values.”

Further reading

Edge cases worth rehearsing

Interviewers and production incidents cluster around the same edges: first render versus update, empty and loading states, Strict Mode double setup, concurrent interruptions, and what happens when identity (key, route params, user id) changes mid-edit. Walk one concrete user journey end-to-end — open, edit, navigate away, come back — and say which state survives.

Prefer fixing data flow and ownership before reaching for memoization or micro-optimizations. Prefer event handlers over effects when a user action is the trigger. Prefer deriving values during render over mirroring props into state. Prefer stable list keys from business ids. Measure with the profiler when performance is the claim.

When you cite an API, mention one failure mode: abort on unmount, serializable props across server/client boundaries, focus restoration for dialogs, or cache invalidation after a mutation. Specific beats generic every time.

Related guides