Hooks and effects
Follow the Rules of Hooks
Call Hooks at the top level of React components and custom Hooks so the call order stays stable between renders.
Hooks must run at the top level of a React component or custom Hook. React matches Hook state by call order. That order must stay the same on every render.
Do not call a Hook inside a condition:
if (isLoggedIn) {
const [profile, setProfile] = useState(null)
}
If one render calls the Hook and the next render skips it, every later Hook can be matched with the wrong stored value. State from useState can end up attached to the wrong variable. The bug is hard to trace because it depends on which branch ran.
Call the Hook unconditionally, then branch with the result:
const [profile, setProfile] = useState(null)
if (!isLoggedIn) {
return <Login />
}
Do not call Hooks in loops, nested callbacks, event handlers, or after an early return. Call them only while React is rendering a component or another Hook.
Put a condition inside an Effect when the synchronization itself is conditional. Split the component when two branches genuinely need different stateful structures.
Some data Hooks accept a value that pauses their work. You still call the Hook on every render, but move the condition into its argument. SWR, for example, skips a request when its key is null:
const { data } = useSWR(isLoggedIn ? '/api/user' : null, fetcher)
Use the React Hooks linter. It can catch invalid call positions and missing Effect dependencies before they become timing bugs. The eslint-plugin-react-hooks rule runs in most React starter templates. If your editor underlines a Hook call, fix the call site before shipping.
Custom Hooks follow the same rules as components. Every Hook inside useSomething() must also run unconditionally at the top level of that custom Hook.
Move one Hook below an early return and run the linter. Then restore it to the top level and explain why the order is stable again.
Lesson completed