
A Practical Guide to React Hooks: useState, useEffect and the Patterns That Actually Hold Up
Hooks are simple in isolation and surprisingly easy to misuse once a component grows. Most of the bugs that show up later trace back to a handful of habits around useState and useEffect that felt harmless when they were written.
TL;DR / Written for skimmers
A value that can be computed directly from existing state or props does not need its own useState. Storing it separately just creates a second source of truth that can drift out of sync with the value it was derived from. The clearest useEffect is the one whose dependency array reads like a sentence: when this value changes, do this thing. An effect that exists to implement a whole feature, with a long body and a vague dependency list, is usually several effects pretending to be one. Any effect that subscribes to something, starts a timer, or opens a connection needs a cleanup function that undoes exactly that. Skipping cleanup is how duplicate subscriptions and memory leaks creep into a component that otherwise looks correct. A custom hook earns its name when it actually uses React state, effects or context internally. If a function does none of that, it is a regular helper function, and naming it useSomething just adds confusion about whether it follows the rules of hooks.
useState is for values, not for derived data
A value that can be computed directly from existing state or props does not need its own useState. Storing it separately just creates a second source of truth that can drift out of sync with the value it was derived from.
Computing it inline during render, or with useMemo if the computation is expensive, keeps that value always correct without an extra effect to keep it updated.
useEffect should react to a value changing, not to a feature
The clearest useEffect is the one whose dependency array reads like a sentence: when this value changes, do this thing. An effect that exists to implement a whole feature, with a long body and a vague dependency list, is usually several effects pretending to be one.
Splitting a large effect by what it depends on, rather than by what feature it belongs to, makes the dependency array honest and the effect easier to reason about in isolation.
Cleaning up after yourself
Any effect that subscribes to something, starts a timer, or opens a connection needs a cleanup function that undoes exactly that. Skipping cleanup is how duplicate subscriptions and memory leaks creep into a component that otherwise looks correct.
React calls the cleanup function before the effect runs again and when the component unmounts, so writing it is less about edge cases and more about treating the effect as a single complete unit: set up, then tear down.
When a custom hook is just a fancy function in disguise
A custom hook earns its name when it actually uses React state, effects or context internally. If a function does none of that, it is a regular helper function, and naming it useSomething just adds confusion about whether it follows the rules of hooks.
The useful custom hooks are the ones that wrap a recurring stateful pattern, like a debounced value or a media query listener, so that pattern stops being copy-pasted across components.