React Hook Best Practices
Table of Contents + −
In the last lesson you learned about React Custom Hooks, so you’ve now seen every hook there is. Hooks are easy to misuse, so this lesson gathers the best practices into one short checklist that keeps every hook you write clean and predictable.
🤔 Why a best-practices checklist?
Each hook on its own is simple, but put a few together and small mistakes creep in that are hard to spot:
- An effect runs more often than you expect, because its dependency array isn’t honest.
- State seems “one step behind”, because you read it right after setting it.
- A timer or listener keeps running after the component is gone, because nothing cleaned it up.
- A hook is called inside an
if, so it runs on some renders and not others.
This checklist is the set of habits that stop those exact bugs before they happen.
📜 Follow the rules of hooks
Everything starts here. The two rules of hooks are not suggestions, they’re what makes hooks work at all:
- Only call hooks at the top level of your component or custom hook. Never inside an
if, a loop, or a nested function. - Only call hooks from React functions: components, or other custom hooks. Not from regular JavaScript functions.
Here’s why that matters:
- React tracks hooks by their order, so that order must be the same on every render.
- An
ifaround a hook breaks that order and corrupts the state, so keep them at the top, always.
// ❌ hook inside a condition - order changes between rendersif (isLoggedIn) { const [name, setName] = useState("");}
// ✅ hook at the top level, condition inside if neededconst [name, setName] = useState("");if (isLoggedIn) { // use name here}Let the linter help
The official eslint-plugin-react-hooks checks both rules for you and warns about missing dependencies. Turn it on. It catches most hook mistakes before you even run the app.
🎯 Write honest dependency arrays
Most useEffect and useMemo bugs come from a dishonest dependency array. The fix is to be truthful about what the code uses.
- List every value the effect or calculation reads: props, state, and functions.
- Don’t lie to silence a warning by leaving things out. A missing dependency means stale values.
- If an effect runs too often because a function changes each render, wrap that function in
useCallback.
So the rule is one line: the array must match what the code actually uses, no more and no less.
// ❌ uses count but doesn't list it - effect sees an old countuseEffect(() => { console.log(count);}, []);
// ✅ list everything the effect readsuseEffect(() => { console.log(count);}, [count]);🧹 Always clean up effects
If an effect starts something that keeps going, it must stop it too. Otherwise that thing piles up.
- Return a cleanup function from
useEffectfor timers, intervals, subscriptions, and event listeners. - React runs the cleanup before the next effect run and when the component unmounts.
- Without cleanup you get leaks, like several intervals running at once or listeners on a gone component.
So the habit is simple. If you setInterval, addEventListener, or subscribe to anything, return the matching cleanup.
useEffect(() => { const id = setInterval(() => console.log("tick"), 1000); return () => clearInterval(id); // ✅ stop it on cleanup}, []);🔄 Use the functional updater for state from state
When the new state depends on the old state, don’t read the variable directly. Use the function form of the setter.
setCount(count + 1)readscountfrom this render, which can be stale in quick updates.setCount((prev) => prev + 1)always gets the latest value React has.- This matters a lot in effects, timers, and event handlers where the value may have moved on.
So whenever the next value is built from the current value, reach for the updater function.
// ❌ may use a stale count if updates batch or run latersetCount(count + 1);
// ✅ always based on the latest valuesetCount((prev) => prev + 1);🚦 Know when NOT to use a hook
The best hook is sometimes no hook. A lot of code reaches for useEffect or useMemo when plain code would be clearer.
- Don’t use
useEffectto compute a value from props or state. Just calculate it during render. - Don’t use
useMemooruseCallbackfor cheap work or when nothing compares the result. The overhead isn’t worth it. - Don’t copy a prop into state just to read it. Use the prop directly, or you’ll fight to keep them in sync.
So before adding a hook, ask: do I actually need React to react to something here, or can I just compute it plainly? Often plain code wins.
useEffect is not for everything
A common mistake is using useEffect to derive data, like setting a fullName state whenever first or last name changes. You don’t need state or an effect for that, just compute const fullName = first + " " + last during render.
✅ Best Practices
Here’s the whole checklist in one place, so you can glance at it any time.
- Call hooks only at the top level, and only from components or custom hooks.
- Keep dependency arrays honest: list everything the code reads.
- Clean up every effect that starts a timer, listener, or subscription.
- Use the functional updater when new state depends on old state.
- Extract repeated hook logic into a
use-named custom hook. - Skip the hook when plain code is clearer, especially for derived values.
- Turn on
eslint-plugin-react-hooksand trust its warnings.
🧩 What You’ve Learned
- ✅ Always follow the two rules of hooks: top level only, and only in React functions
- ✅ Dependency arrays must list every value the effect or calculation reads, or you get stale data
- ✅ Clean up effects that start timers, listeners, or subscriptions to avoid leaks
- ✅ Use the functional updater when the next state depends on the current state
- ✅ Don’t use
useEffectto derive a value you can just compute during render - ✅ The linter (
eslint-plugin-react-hooks) catches most of these mistakes for you
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Why must hooks be called at the top level, not inside an if or loop?
Why: React identifies each hook by the order it is called. A condition around a hook changes that order between renders and corrupts the state.
- 2
What belongs in a useEffect dependency array?
Why: The array must honestly list everything the effect uses. Leaving items out causes the effect to run with stale values.
- 3
When should you return a cleanup function from useEffect?
Why: Anything that keeps running, like an interval or event listener, must be stopped in a cleanup function so it doesn't pile up or leak after unmount.
- 4
What is a sign you should NOT use useEffect?
Why: If a value can be calculated directly from props or state during render, you don't need an effect or extra state for it. Compute it in render instead.
🚀 What’s Next?
That wraps up the React Hooks module. You now know every main hook, how to build your own, and the habits that keep them all clean. Next we put hooks to real use by talking to the outside world, starting with what an API is and how your React app calls one.