Skip to main content
Engineering Blog
Engineering Blog2026-07-18

React Lifecycle Mechanics: Mapping Component Synchronization to useEffect

Quan Van|Technical Essay|4 min read

Understanding the lifecycle of a component is a crucial turning point in truly mastering React.

In the era of Class Components, engineers structured their mental models around discrete lifecycle methods (such as componentDidMount or componentDidUpdate). However, with the advent of Functional Components and Hooks, this paradigm shifted. We no longer think in terms of "lifecycles"; instead, we think in terms of synchronization—keeping our external systems synchronized with React's State and Props via useEffect.

Below is a comprehensive overview of the React lifecycle and how it maps perfectly to the useEffect hook.


1. The Three Core Phases of a Component

Regardless of whether you are using Classes or Functions, the lifecycle of a React component always progresses through three primary phases:

  1. Mounting: The component is initialized and inserted (rendered) into the DOM for the very first time.
  2. Updating: A component currently displayed on the screen receives new data (State or Props), forcing React to recalculate and re-render it.
  3. Unmounting: The component is no longer needed and is completely removed from the DOM.

2. Mapping the Lifecycle to useEffect

The useEffect hook is designed to consolidate all lifecycle-related logic into a single location. Its basic syntax is: useEffect(setupFunction, dependenciesArray).

How it behaves in relation to the component lifecycle depends entirely on the dependency array passed as the second argument.

Case 1: Run strictly once on Mount

  • Class Equivalent: componentDidMount
  • Implementation: Pass an empty array [].
  • Use Cases: Initial API data fetching, setting up global event listeners, or initializing setTimeout/setInterval.
useEffect(() => {
  console.log("Component has been mounted to the DOM!");
  // Execute initial API calls here...
}, []); // <-- The empty array is the key

Case 2: Run on Mount AND when data changes (Updating)

  • Class Equivalent: componentDidMount + componentDidUpdate
  • Implementation: Pass specific variables (State/Props) into the dependency array. The Effect will execute on the initial mount, and then re-execute only when the specified variables change.
  • Use Cases: Fetching new data when a searchKeyword changes, or updating the document title based on a count.
const [count, setCount] = useState(0);

useEffect(() => {
  console.log(`Count value changed to: ${count}`);
  document.title = `You clicked ${count} times`;
}, [count]); // <-- Only re-runs when 'count' mutates

Case 3: Cleanup before Unmount (or before the next Effect)

  • Class Equivalent: componentWillUnmount
  • Implementation: Return a cleanup function from within the useEffect.
  • Use Cases: Clearing a setInterval, aborting pending API requests, or removing event listeners to prevent severe memory leaks.
useEffect(() => {
  const timer = setInterval(() => {
    console.log("Counting...");
  }, 1000);

  // This returned function acts as the Cleanup phase
  return () => {
    console.log("Component is unmounting (or Effect is re-running). Cleaning up!");
    clearInterval(timer); 
  };
}, []); 

Note: The cleanup function does not solely run during Unmounting. If your dependency array contains variables (e.g., [count]), React will execute the previous Effect's cleanup function before running the NEW Effect to clear out residual state.

Case 4: Run after EVERY render (Highly discouraged)

  • Implementation: Omit the dependency array entirely (do not pass a second argument).
  • Consequences: The Effect will trigger after every single render triggered by any State or Prop change. If you mutate state inside this Effect, it will cause an infinite rendering loop.
useEffect(() => {
  console.log("I run after every single component render!");
}); // <-- Notice the missing [] array


3. Quick Reference Cheatsheet

useEffect Syntax Behavioral Mapping (Lifecycle)
useEffect(fn) Runs after every render (Prone to infinite loops).
useEffect(fn, []) Runs only once (Mounting phase).
useEffect(fn, [x, y]) Runs on Mount, AND whenever x or y mutates (Updating phase).
return () => {} (inside fn) Cleans up before Unmount, OR before the next Effect execution.

[!NOTE] Engineering Pro-Tip: A very common anti-pattern is "lying" to React about your dependencies (using a variable inside the Effect but intentionally omitting it from the [] array to prevent re-runs). This inevitably leads to "stale state" bugs that are notoriously difficult to trace. Always provide a complete dependency array. If your Effect is running too frequently, you need to refactor your architectural logic, not delete variables from the array.

End of article

Want to link PomaiDB into your project?

Read the Engineering Manual