A Retrospective on React Hooks
Hooks fundamentally changed React.They enable us to compose behavior in ways class components never could.But they also introduced the "Rules of Hooks" and the dependency array confusion.
The Sync Mental Model
Before Hooks, we thought in "Lifecycle Methods": componentDidMount , componentDidUpdate, componentWillUnmount . We mapped our code to time.
With Hooks, especially useEffect , we must think in Synchronization.The question isn't "When does this run?" The question is "What state is this effect matching?"
// Wrong: Thinking in time
useEffect(() => {
fetchData();
}, []); // "Run once on mount"
// Right: Thinking in synchronization
useEffect(() => {
fetchData(query);
}, [query]); // "Keep data synchronized with query"
Stale Closures: The New "this"
In classes, this.state.count always pointed to the latest count. In Hooks, we deal with closures. If your Effect captures a variable from the first render, it will gaze into the past forever.
The solution is not to lie to the dependency array.If you use it, list it.If listsing it causes infinite loops, your Effect is likely doing too much or your logic structure is wrong(e.g., defining functions inside components without useCallback ).
Custom Hooks: The Composition Superpower
This is where Hooks shine.In the Class era, sharing non - visual logic required "Higher Order Components"(HOCs) or "Render Props." Both led to "Wrapper Hell."
Custom hooks allow us to extract stateful logic just like we extract utility functions.
function useWindowSize() {
const [size, setSize] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setSize(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
This useWindowSize hook can be used in any component.The state is isolated, but the logic is shared.This has led to the explosion of hook libraries(useHooks, react - use).
The Rules of Hooks
Hooks rely on call order.React knows that the first useState call corresponds to the first state variable because it relies on the index in an internal array. This is why you cannot put Hooks for loops or if statements. It feels magical and fragile initially, but it's the constraint that enables the conciseness.
useContext + useReducer != Redux
A common 2020 trope was "You don't need Redux anymore." While useContext + useReducer can manage global state, they lack the performance optimizations of Redux(selector subscriptions).Updating a Context value re - renders all consumers. For complex state, purpose-built libraries like Redux Toolkit, Zustand, or Jotai are still superior.
Conclusion
Hooks have won.They are the default.Class components are legacy.The learning curve is stepper(you need to understand closures deeply), but the expressiveness and composability are worth it.We are writing less code and building better abstractions.