Back to Blog
Lesson 36 of the React Fundamentals: Build Modern UIs from Scratch course
ReactJune 25, 20264 min read

Review of Component Lifecycle: Mastering React Internals

Master the React lifecycle to write more predictable code. Learn how to map component mounting, updating, and unmounting phases to the hooks you use daily.

reactlifecyclehooksuseeffectfrontendweb developmentjavascript

Previously in this course, we explored how to handle asynchronous data and side effects in fetching data from an API and cleanup functions in useEffect. Now that we’ve built complex features like debounced search and custom hooks, it’s time to zoom out and look at the "big picture" of how React manages a component's existence.

Understanding the component lifecycle is the difference between writing "code that works" and writing "code that is robust." When you grasp the order of operations, you stop guessing why a state update triggered a re-render or why your API call fired twice.

The Three Phases of the Lifecycle

Every component in React moves through three distinct phases. While modern functional components use hooks rather than the old class-based methods, the underlying mechanics remain the same:

  1. Mounting: The component is being born. React creates the component instance, runs your function body, and inserts the resulting DOM nodes into the browser.
  2. Updating: The component is growing or changing. This happens when props change, state is updated, or the parent component re-renders.
  3. Unmounting: The component is being destroyed. React removes the component from the DOM tree, and it's time to clean up any lingering resources.

Mapping Lifecycle Phases to Hooks

In the functional world, we don't have componentDidMount or componentWillUnmount. Instead, we use useEffect to synchronize our components with these lifecycle moments.

  • Mounting: Represented by useEffect(() => { ... }, []). The empty dependency array tells React to run this effect exactly once after the initial render.
  • Updating: Represented by useEffect(() => { ... }, [dependencies]). The effect runs whenever any value in the array changes.
  • Unmounting: Represented by the function returned inside useEffect. If your effect returns a function, React runs that function right before the component is removed from the DOM.

Worked Example: Tracking the Lifecycle

Let’s look at a simple MovieDetail component to see these phases in action. We want to log when the component mounts, when it receives a new movie ID, and when it disappears.

JSX
import { useEffect } from CE9178">'react';

function MovieDetail({ movieId }) {
  useEffect(() => {
    // 1. Mounting phase
    console.log("MovieDetail component mounted!");

    // 3. Unmounting phase(Cleanup)
    return () => {
      console.log("MovieDetail component unmounted!");
    };
  }, []); // Empty array = mount only

  useEffect(() => {
    // 2. Updating phase
    console.log(CE9178">`MovieDetail updated for movie: ${movieId}`);
  }, [movieId]); // Runs when movieId changes

  return <div>Viewing details for movie {movieId}</div>;
}

The Order of Operations

It's common to assume that the code inside your component function runs after the DOM is updated. That's not entirely true.

  1. Render Phase: React calls your function component. This is pure JavaScript logic. React calculates what the UI should look like.
  2. Commit Phase: React applies the changes to the real DOM.
  3. Layout/Effect Phase: Once the browser has painted the screen, React runs your useEffect hooks.

This separation is why we never trigger state updates directly in the component body—that would cause an infinite loop during the Render Phase. We always delegate side effects to the Effect Phase. For a deeper dive into controlling this flow, revisit Mastering useEffect Dependencies: Control Your React Lifecycle.

Hands-on Exercise

Open your project and navigate to your MovieCard component.

  1. Add a useEffect that logs "Card rendered" to the console.
  2. Add a cleanup function to that same useEffect that logs "Card removed".
  3. Wrap the MovieCard in a conditional check (e.g., showCard && <MovieCard />).
  4. Toggle the showCard state and observe the console. Does the cleanup function run when you hide the card?

Common Pitfalls

  • Forgetting the Dependency Array: If you omit the array, your effect runs on every render, which often leads to infinite loops if you are calling setState inside the effect.
  • Assuming Synchronicity: Remember that useEffect is asynchronous. The UI is updated before the effect runs. If you try to read DOM measurements (like div.offsetWidth) inside the effect, you are safe; doing it in the render body is unreliable.
  • Not Cleaning Up: Failing to return a cleanup function for event listeners or intervals is a leading cause of memory leaks. As we discussed in Cleanup Functions in useEffect: Preventing Memory Leaks, always clean up what you create.

Recap

The React lifecycle is a sequence of events: mounting, updating, and unmounting. We use useEffect to tap into these moments. By keeping your render logic pure and moving side effects into the appropriate lifecycle hooks, you ensure your components are performant and easy to debug.

Up next: We'll dive into the nuances of state management and how to choose the right strategy for complex applications.

Similar Posts