Skip to content
chornous.dev

Type two or more letters. Esc closes.

Index of sheets
Theme

Sheet 03 · Writing

All notes

React state without the noise

Derive what you can, store what changes, and keep effects for the systems React doesn't own.

Pl. 28 · tree drawing generated from the slug “react-state-without-the-noise”

Most of the React state bugs I fix come from storing too much.

If you can derive a value from props, the URL or other state, a second copy in state gives you a synchronization problem. It won't show on the first render. It shows up later, once filters, async saves or optimistic updates arrive on the screen.

Keep the state surface small

My order of preference:

  1. Derive values during render when they're cheap and deterministic.
  2. Use state for values that change through user input or an async lifecycle.
  3. Use refs for browser values that shouldn't trigger a render.
  4. Use effects to sync with systems outside React.

Hooks written in that order stay readable, and the component is easy to split when it grows.

type Filter = "all" | "featured";

function getVisibleProjects<T extends { featured: boolean }>(
  projects: T[],
  filter: Filter,
): T[] {
  if (filter === "featured") {
    return projects.filter((project) => project.featured);
  }

  return projects;
}

The function is small, and it prevents a common mistake: storing the filtered list in state and then fighting to keep it in step with the source.

Effects are bridges

An effect should read like a bridge to something React doesn't own: a subscription, a timer, a physics engine, a browser API, local storage. An effect that only sets state you could have derived during render hides complexity you didn't need.

I count how many places claim authority over the same piece of UI, and I try to get that number to one.

Volodymyr Chornous