When to Use component-did-update in React: Best Practices and Lifecycle Guide

Use componentDidUpdate when you need to perform side effects—such as network requests or DOM measurements—after a component's output has been committed to the DOM, while always comparing prevProps or prevState to prevent infinite update loops.

The componentDidUpdate lifecycle method serves as the primary hook for responding to prop and state changes in React class components. As implemented in the facebook/react repository, this method fires immediately after every update except the initial render, making it essential for synchronizing your component with external systems. Understanding when to implement component-did-update in react applications requires examining its relationship to deprecated lifecycles and its specific execution timing within the render commit phase.

What Is componentDidUpdate?

componentDidUpdate(prevProps, prevState, snapshot) is invoked immediately after updating occurs. This method is not called for the initial render, making it distinct from componentDidMount. According to the React source in packages/react/src/ReactBaseClasses.js, this lifecycle represents the final stage of the update cycle where the DOM reflects the latest rendered output.

The method receives three arguments:

  • prevProps: The props before the current update
  • prevState: The state before the current update
  • snapshot: The value returned from getSnapshotBeforeUpdate, or null if that method is not defined

Why componentDidUpdate Replaces Legacy Lifecycles

React deprecated componentWillUpdate and componentWillReceiveProps because they execute before the DOM updates, creating risks for inconsistent UI states and race conditions. The test suite in packages/react/src/__tests__/createReactClassIntegration-test.js (lines 622-635) explicitly warns against these legacy methods and directs developers to move side-effect logic to componentDidUpdate.

By moving side effects to componentDidUpdate, you guarantee that the DOM reflects the new props and state before you interact with it. This eliminates the "stale UI" problems that plagued the older lifecycle methods.

When to Use componentDidUpdate: Decision Checklist

Fetching Data After Prop Changes

Use componentDidUpdate to trigger network requests when specific props change. This ensures the UI reflects the new prop before initiating the fetch, preventing visual inconsistencies.

componentDidUpdate(prevProps) {
  if (prevProps.userId !== this.props.userId) {
    this.fetchUserData(this.props.userId);
  }
}

Reading DOM Layout and Measurements

Because componentDidUpdate runs after the DOM commit, it is the safe place to read layout properties like scrollHeight, offsetWidth, or element positions without triggering layout thrashing.

Updating State Based on Previous Props

While generally discouraged, if you must update state in response to prop changes, do it here with strict equality checks to prevent infinite loops:

componentDidUpdate(prevProps, prevState) {
  if (this.state.count !== prevState.count) {
    this.setState({lastUpdated: Date.now()});
  }
}

What to Avoid in componentDidUpdate

  • One-off initialization logic: Use componentDidMount for setup that runs once.
  • Cleanup operations: Use componentWillUnmount for subscription teardowns.
  • Deriving state from props: Prefer static getDerivedStateFromProps or memoization instead of setting state here.

Relationship to Other React Lifecycle Methods

The React update cycle follows a strict sequence defined in the source code. According to packages/react/src/__tests__/ReactES6Class-test.js, the update path proceeds through render commitment before invoking componentDidUpdate.


Update Phase:
render() → (DOM commit) → componentDidUpdate(prevProps, prevState, snapshot)

The getSnapshotBeforeUpdate Pairing

When you need information from the DOM just before it changes (such as scroll position), implement getSnapshotBeforeUpdate. The framework enforces that this snapshot value is passed as the third argument to componentDidUpdate.

The server-side rendering logic in packages/react-server/src/ReactFizzClassComponent.js (lines 558-600) validates this contract, logging errors if the snapshot parameter is mismanaged.

getSnapshotBeforeUpdate(prevProps, prevState) {
  if (prevProps.messages.length < this.props.messages.length) {
    return this.listRef.scrollHeight - this.listRef.scrollTop;
  }
  return null;
}

componentDidUpdate(prevProps, prevState, snapshot) {
  if (snapshot !== null) {
    this.listRef.scrollTop = this.listRef.scrollHeight - snapshot;
  }
}

Practical Code Examples

Responding to Specific Prop Changes

This pattern from packages/react/src/__tests__/createReactClassIntegration-test.js demonstrates the canonical approach to data fetching in response to prop changes:

class UserProfile extends React.Component {
  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser(this.props.userId);
    }
  }

  fetchUser(id) {
    fetch(`/api/users/${id}`)
      .then(r => r.json())
      .then(data => this.setState({user: data}));
  }

  render() {
    const {user} = this.state || {};
    return user ? <h1>{user.name}</h1> : <Spinner />;
  }
}

Preserving Scroll Position with getSnapshotBeforeUpdate

When implementing chat interfaces or feed components, combine getSnapshotBeforeUpdate with componentDidUpdate to maintain user scroll position during updates:

class ChatLog extends React.Component {
  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevProps.messages.length < this.props.messages.length) {
      return this.listRef.scrollHeight - this.listRef.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    if (snapshot !== null) {
      this.listRef.scrollTop = this.listRef.scrollHeight - snapshot;
    }
  }

  render() {
    return (
      <ul ref={el => (this.listRef = el)}>
        {this.props.messages.map(m => (
          <li key={m.id}>{m.text}</li>
        ))}
      </ul>
    );
  }
}

Guarding Against Infinite Loops

Always compare previous and current values before calling setState in componentDidUpdate to avoid infinite render cycles:

class Counter extends React.Component {
  state = {count: 0};

  componentDidUpdate(_, prevState) {
    if (this.state.count !== prevState.count) {
      this.logChange(this.state.count);
    }
  }

  increment = () => this.setState(s => ({count: s.count + 1}));
}

Key Source Files in the React Repository

Understanding the implementation details of component-did-update in react requires examining these specific files from the facebook/react codebase:

Summary

  • Use componentDidUpdate for side effects that depend on the DOM being updated, such as network requests or measurements, but only after comparing prevProps or prevState to prevent infinite loops.

  • Avoid deprecated patterns by moving logic from componentWillUpdate and componentWillReceiveProps to componentDidUpdate, as enforced by the React test suite in createReactClassIntegration-test.js.

  • Pair with getSnapshotBeforeUpdate when you need pre-update DOM information, ensuring the snapshot value is received as the third parameter in componentDidUpdate per the contract in ReactFizzClassComponent.js.

  • Never use for initialization or cleanup—reserve componentDidMount for one-time setup and componentWillUnmount for teardown operations.

Frequently Asked Questions

When should I use componentDidUpdate instead of componentDidMount?

Use componentDidMount for one-time initialization logic that runs after the initial render, such as setting up subscriptions or fetching initial data. Use componentDidUpdate for logic that must run in response to specific prop or state changes after subsequent renders, such as refetching data when an ID changes or measuring updated DOM elements.

How do I prevent infinite loops when using componentDidUpdate?

Always compare the current props or state against the previous values received as arguments before calling setState. For example, check if (prevProps.userId !== this.props.userId) before fetching new data. Without this guard, calling setState inside componentDidUpdate triggers another update immediately, creating an infinite loop that crashes the application.

What is the relationship between getSnapshotBeforeUpdate and componentDidUpdate?

getSnapshotBeforeUpdate captures information from the DOM immediately before changes are committed, such as scroll position or element dimensions. React then passes this snapshot value as the third argument to componentDidUpdate, allowing you to restore or reference the pre-update state after the DOM has been modified. The React source code in packages/react-server/src/ReactFizzClassComponent.js enforces that these two methods work as a pair.

Why are componentWillUpdate and componentWillReceiveProps deprecated in favor of componentDidUpdate?

These legacy lifecycles execute before the render output is committed to the DOM, which made it easy to introduce inconsistencies by performing side effects on data that didn't yet match the visual output. According to the React test suite in packages/react/src/__tests__/createReactClassIntegration-test.js (lines 622-635), componentDidUpdate is the recommended replacement because it guarantees the DOM reflects the new props and state before any side effects run, eliminating stale UI problems.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →