Hooks & Functional Lifecycle

Chapter 3: Hooks & Functional Lifecycle

React Hooks are primitives that allow functional components to hook into React's internal state and lifecycle engines. Unlike class-based lifecycles, Hooks follow a declarative model based on Dependency Tracking and Fiber-node persistence. This model ensures that side effects are synchronized with the component's data flow, preventing common bugs associated with manual lifecycle management.

I. The useEffect Specification

The useEffect hook is the standard API for managing side effects. Internally, React schedules these effects to run asynchronously after the browser has completed its painting cycle. This is technically managed by the Passive Effect Phase of the Fiber cycle. When a component with an effect is committed, React adds it to a global "Effect List." Once the main browser thread is idle after the DOM update and layout paint, the Scheduler picks up these tasks. By using the browser's postMessage or MessageChannel APIs to schedule these effects as Macrotasks, React ensures that side-effect logic (like API calls or logging) does not block the user's perception of UI responsiveness.

1. The Effect Lifecycle & Fiber Commit

CommitDOM UpdatePassive PhaseScheduler: IdleuseEffect() RunDependencyReferential CheckCleanup → Re-runUnmountFinal Cleanup

2. The Cleanup Phase & Resource Management

If an effect returns a function, React will execute it before the component unmounts or before the next re-run. React uses Update Flags (bitwise tags like HasEffect, Layout, Passive) on each Fiber node to track which effects are dirty. During the commit phase, the reconciler traverses the tree and executes any pending "destroy" (cleanup) functions from the previous render before calling the "create" functions of the current render. This strict sequential execution ensures that resources like event listeners are cleanly released before a new one is initialized, preventing memory leaks and event listener duplication.


II. The useLayoutEffect Variant & Layout Thrashing

While useEffect is asynchronous, useLayoutEffect fires synchronously after DOM mutations but before the paint.

  • The Execution Window: It runs in the same tick as the DOM update. Use this for measuring element dimensions or positioning before the browser draws the frame.
  • Layout Thrashing: If you update state inside useLayoutEffect, React performs a synchronous re-render. This can lead to "Layout Thrashing" where the browser is forced to recalculate styles multiple times in a single frame, causing significant drop in FPS.

III. Strict Rules of Hooks & Fiber Pointers

To maintain the internal pointer to the hook state linked list, React enforces two fundamental rules:

  1. Top-Level Only: Do not call hooks inside loops or conditions. React relies on the call order to map the current hook to the correct Fiber node object (workInProgress.memoizedState).
  2. React Functions Only: Only call hooks from React components.

IV. Production Anti-Patterns

  • Missing Dependencies: Omitting a used variable from the useEffect dependency array. This creates a closure over stale state/props, leading to "Zombie Effects" that operate on outdated data.
  • Syncing State via Effect: Using useEffect to update a state variable whenever a prop changes. This results in an extra re-render cycle (Prop Change → Render → Effect → State Update → Render). Solution: Use derived state or key props to reset component state.
  • Heavy Computation in Effect: Performing synchronous data processing in an effect, blocking the event loop and delaying the next frame.

VI. Performance Bottlenecks

  • Effect Jitter: An effect that triggers an update which in turn causes the effect to re-run (infinite loop or high-frequency cycling).
  • Layout Blocking: Overusing useLayoutEffect for non-layout tasks (e.g., API calls). This forces the browser to wait for the JS execution to finish before painting, resulting in a "white screen" or laggy UI.