Library
Learning Patterns · 9 of 12
Learning Patterns
UI/UX Design CRITICAL

The Hooks Revolution: Replacing Legacy Patterns

Learning Patterns Lydia Hallie & Addy Osmani
react hooks useState useEffect custom-hooks HOC render-props class-components refactoring

Key Principle

React Hooks reorganize component logic by concern rather than by lifecycle phase. Class components forced unrelated logic into shared lifecycle methods (componentDidMount, componentWillUnmount) while splitting related logic across them. Hooks invert this: each hook owns one concern end-to-end, co-locating setup and teardown regardless of when they fire.

Why This Matters

The Three Structural Problems with Class Components

  1. Class overhead blocks incremental change. Adding state to a functional component required a full rewrite into a class — constructor, super(), this binding, render(). This is not an incremental addition but a structural transformation that risks accidentally changing behavior while also requiring ES2015 class knowledge. (Ch. 6, Hooks Pattern)

  2. Code-sharing patterns create wrapper hell. HOCs and Render Props produce deeply nested component trees (<WrapperOne>...<WrapperFive><Component>>). The nesting obscures data flow, making it harder to trace prop origins and debug unexpected behavior. (Hooks Pattern)

  3. Lifecycle methods force the wrong grouping axis. A component tracking both count and window width must interleave unrelated state in this.state, split related resize logic across componentDidMount/componentWillUnmount, and scatter counter logic elsewhere. Result: tangled unrelated logic within methods, duplicated setup/teardown across methods. (Hooks Pattern)

Additional Class Liabilities

JavaScript classes minify poorly, break hot reloading, and force this-binding boilerplate. Classes also encourage inheritance hierarchies for logic reuse, increasing coupling. (Hallie & Osmani, "Learning Patterns")

Good Examples

Lifecycle-to-useEffect Mapping

Class Method useEffect Equivalent
componentDidMount useEffect(fn, [])
componentWillUnmount useEffect(() => () => cleanup, [])
componentDidUpdate useEffect(fn) (no dependency array)

Refactoring Heuristic

Identify state --> replace this.state/this.setState with useState --> replace lifecycle methods with useEffect --> drop the class wrapper. (Ch. 7: Hooks Pattern)

Custom Hook Extraction Pattern

Identify duplicated stateful logic across components, extract into a use-prefixed function, parameterize via arguments, return relevant state. Each custom hook owns one concern end-to-end, and the unit of test aligns with the unit of concern. (Ch. 7: Hooks Pattern)

useEffect Dependency Array

  • Empty [] = mount-only
  • No array = every render
  • Specific values = re-run only on those changes
  • Return function = cleanup on unmount or before re-run

Counterpoints

  • Hooks shift complexity rather than remove it. They impose implicit rules (call order, top-level-only) that are invisible without a linter plugin. Misuse of useEffect, useCallback, useMemo introduces subtle bugs — complexity moves from structural boilerplate to runtime correctness discipline. (Hallie & Osmani, "Learning Patterns")
  • HOCs remain valid when applying identical cross-cutting concerns declaratively (styling, auth, global state), though they create nested hierarchies visible in DevTools that Hooks avoid.
  • useContext re-renders its consumer on every context value change — indiscriminate context usage causes performance problems because the re-render boundary is the consumer, not the provider. (Ch. 7)

Key Quotes (with citations)

  • "Although Hooks are not necessarily a design pattern, Hooks play a very important role in your application design. Many traditional design patterns can be replaced by Hooks." (Ch. 6)
  • "Besides having to make sure you don't accidentally change any behavior while refactoring the component, you also need to understand how ES2015 classes work." (Hooks Pattern)
  • "Having many wrapping components in order to share code among deeper nested components can lead to something that's best referred to as a wrapper hell." (Hooks Pattern)
  • "Logic within that component can get tangled and unstructured, which can make it difficult for developers to understand where certain logic is used in the class component." (Hooks Pattern)
  • "By using React Hooks instead of a class component, we were able to break the logic down into smaller, reusable pieces that separated the logic." (Ch. 7: React Hooks Pattern)
  • "The issues that we tried to solve with render props, have largely been replaced by React Hooks." (Ch. 5)
  • "JavaScript classes can be difficult to manage, hard to use with hot reloading and may not minify as well." (Hallie & Osmani, "Learning Patterns")
  • "It's important to start your hooks with use in order for React to check if it violates the rules of Hooks." (Hooks Pattern)

Rules of Thumb

  1. If you are rewriting a functional component to a class just to add state, use useState instead. The class rewrite tax is eliminated.
  2. If setup and teardown logic are paired, keep them in one useEffect. Splitting them across lifecycle methods caused drift bugs in class components.
  3. If two components share the same stateful logic, extract a custom hook. The use prefix is mandatory — React's linter depends on it.
  4. If you see deeply nested wrapper components, suspect HOC/render-prop wrapper hell. Hooks flatten the component tree.
  5. If next state depends on previous state or involves multiple sub-values, prefer useReducer over useState. It centralizes transition logic and enables performance optimization.

Related References

  • HOC Pattern: naming collisions, implicit props — the problems render props solved
  • Render Props Pattern: explicit data passing but no lifecycle attachment — the ceiling Hooks broke through
  • Causal chain: Implicit props (HOC) --> explicit passing (Render Props) --> lifecycle ceiling (Render Props) --> Hooks subsume both
  • React Context API: useContext connects to global state without prop drilling
  • Redux-style reducers: useReducer mirrors the pattern for complex local state