Hooks & Closures
The problem
Components often need to run side effects (fetching data, subscribing to events) synchronized with rendering, and need to share stateful logic between components without the deep wrapper nesting older patterns required — hooks let function components have state and lifecycle behavior directly.
Why now
Functions and scope already covered closures as functions that remember their creation environment; hooks are built entirely on that mechanism — each render creates a new closure over that render's specific state and props, which explains both hooks' power and their most common pitfall (stale closures).
Mental model
Every render of a component creates a fresh closure, and any hook callback created during that render (an event handler, a useEffect callback) closes over that specific render's values — not the 'current' ones a mental model borrowed from regular variables would expect. A 'stale closure' bug is exactly this: a callback from an old render still referencing that old render's now-outdated state, because it was never re-created.
Requires
- State & Re-RendersReact
Hooks like useEffect are defined in terms of when a component re-renders and what changed; you need the state/re-render model as the timeline hooks are scheduled against.
- Functions & ScopeProgramming Fundamentals
Every hook callback is a closure over its render's scope, and the 'stale closure' problem is a direct, practical instance of the closure behavior already established there — this topic is where that behavior has real, sometimes surprising consequences.
Used in
- useEffect for data fetching, subscriptions, and synchronizing with external systems
- Custom hooks for extracting and reusing stateful logic across components
- useCallback/useMemo for stabilizing closures across renders
- The most common category of subtle React bugs — stale closures in effects and callbacks
Projects
- Write a useEffect that reads a piece of state without listing it in the dependency array, observe the stale-closure bug directly, then fix it
- Extract a repeated piece of stateful logic (e.g. window size tracking) from two components into one shared custom hook
Examples
- useEffect(() => { console.log(count) }, []) always logs the count from the very first render — the closure was only created once, with count frozen at its initial value
- A custom hook useWindowSize() lets any component subscribe to window resize events with a single line, sharing logic instead of duplicating it
Resources
Mastery checklist
- I can explain why each render creates a fresh closure over that render's props and state
- I can identify and fix a stale-closure bug in a useEffect or event handler
- I can explain what a hook's dependency array controls
- I can extract shared stateful logic into a custom hook