nuzairb@gmail.com— press E to copy
FRONTEND ENGINEERING

React Hooks Changed How I Think About State

PUBLISHED ON:March 8, 2021

When React Hooks shipped I treated them as syntax sugar. Cleaner than class components, less boilerplate, same outcome. I rewrote a few components, liked the ergonomics, and moved on.

It took about a year of using them before I understood they were something more fundamental than that.

The class component mental model

In a class component, the unit of organisation is the lifecycle. You put data fetching in componentDidMount. You put cleanup in componentWillUnmount. You put derived state calculation in componentDidUpdate.

The logic for a single feature — say, a data table that fetches from an API, handles loading states, and refreshes when a filter changes — gets split across three or four lifecycle methods. The component file is organised around React's stages, not your feature.

This works. It scales badly. When a component has six features, each split across three lifecycle methods, the cognitive overhead of following any single feature through the file becomes significant.

What hooks changed

Hooks let you organise code by concern instead of by lifecycle stage.

function useTableData(filters: Filters) {
  const [data, setData] = useState<Row[]>([])
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    setLoading(true)
    fetchData(filters).then(rows => {
      setData(rows)
      setLoading(false)
    })
  }, [filters])

  return { data, loading }
}

The fetch logic, the loading state, and the refetch trigger all live together in a custom hook. The component that uses it doesn't need to know any of that. It imports the hook, gets back what it needs.

The component file is now organised around what the component does for the user. The hooks are organised around what each piece of logic does for the component. Both layers are simpler separately than they were combined.

The composability consequence

Because hooks are just functions, they compose. useTableData can use useDebounce which uses useRef and useEffect. Each layer is simple; the composed behaviour is complex.

This is the same principle as function composition in general. Small, well-named functions that do one thing combine into larger behaviours without any single piece being difficult to understand.

I started thinking about state management differently as a result. Not "how do I manage state for this component" but "what is the shape of this concern, and what does a hook for it look like?"

What this changed about how I review code

Before hooks, a long component file was common and somewhat acceptable. There were only so many ways to organise class component code.

After hooks, a long component file is a smell. If a component's render function is doing significant computation, or if it's managing multiple distinct concerns, those concerns should be hooks. The component should read cleanly — mostly JSX, with a handful of hook calls at the top giving it what it needs.

If I'm reviewing a component and I can't understand what it does from its top-level structure in under a minute, the hooks haven't been extracted properly.

The thing no one mentioned

The shift from class components to hooks was positioned as an API change. It was actually a change in how you decompose problems.

Classes organised code around React's internals. Hooks let you organise code around your domain. That's a larger change than the syntax suggests — and it still shapes how I think about every component I write.

Articles in the Same Series

FRONTEND ENGINEERING

The Case for Boring Tech in Client Projects

Feb 14, 2023
FRONTEND ENGINEERING

What Trading UIs Actually Need

Nov 22, 2022
FRONTEND ENGINEERING

What My First Real Client Taught Me About Frontend

Aug 14, 2020
FRONTEND ENGINEERING

Why I Stopped Reaching for Component Libraries First

Nov 13, 2019
FRONTEND ENGINEERING

The Gap Between Knowing JavaScript and Thinking in It

Apr 3, 2019
FRONTEND ENGINEERING

CSS Grid Finally Clicked — Here Is What Made It Make Sense

Jul 4, 2018
FRONTEND ENGINEERING

The Case for Boring Tech in Client Projects

Feb 14, 2023
FRONTEND ENGINEERING

What Trading UIs Actually Need

Nov 22, 2022
FRONTEND ENGINEERING

What My First Real Client Taught Me About Frontend

Aug 14, 2020
FRONTEND ENGINEERING

Why I Stopped Reaching for Component Libraries First

Nov 13, 2019
FRONTEND ENGINEERING

The Gap Between Knowing JavaScript and Thinking in It

Apr 3, 2019
FRONTEND ENGINEERING

CSS Grid Finally Clicked — Here Is What Made It Make Sense

Jul 4, 2018
FRONTEND ENGINEERING

The Case for Boring Tech in Client Projects

Feb 14, 2023
FRONTEND ENGINEERING

What Trading UIs Actually Need

Nov 22, 2022
FRONTEND ENGINEERING

What My First Real Client Taught Me About Frontend

Aug 14, 2020
FRONTEND ENGINEERING

Why I Stopped Reaching for Component Libraries First

Nov 13, 2019
FRONTEND ENGINEERING

The Gap Between Knowing JavaScript and Thinking in It

Apr 3, 2019
FRONTEND ENGINEERING

CSS Grid Finally Clicked — Here Is What Made It Make Sense

Jul 4, 2018
FRONTEND ENGINEERING

The Case for Boring Tech in Client Projects

Feb 14, 2023
FRONTEND ENGINEERING

What Trading UIs Actually Need

Nov 22, 2022
FRONTEND ENGINEERING

What My First Real Client Taught Me About Frontend

Aug 14, 2020
FRONTEND ENGINEERING

Why I Stopped Reaching for Component Libraries First

Nov 13, 2019
FRONTEND ENGINEERING

The Gap Between Knowing JavaScript and Thinking in It

Apr 3, 2019
FRONTEND ENGINEERING

CSS Grid Finally Clicked — Here Is What Made It Make Sense

Jul 4, 2018