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
componentDidMountfor setup that runs once. - Cleanup operations: Use
componentWillUnmountfor subscription teardowns. - Deriving state from props: Prefer
static getDerivedStateFromPropsor 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:
-
packages/react/src/__tests__/createReactClassIntegration-test.js(lines 622-635): Contains deprecation warnings that direct developers away fromcomponentWillUpdatetowardcomponentDidUpdatefor side effects. -
packages/react-server/src/ReactFizzClassComponent.js(lines 558-600): Enforces the contract betweengetSnapshotBeforeUpdateandcomponentDidUpdate, ensuring the snapshot value is properly passed as the third argument. -
packages/react/src/__tests__/ReactES6Class-test.js: Provides concrete tests demonstrating the lifecycle order during component updates. -
packages/react/src/ReactBaseClasses.js: Defines the base class structure and documents the relationship betweencomponentDidUpdateand other lifecycle methods. -
packages/react/src/ReactNoopUpdateQueue.js: Illustrates howforceUpdatetriggers bothcomponentWillUpdateandcomponentDidUpdate, clarifying the update execution path.
Summary
-
Use
componentDidUpdatefor side effects that depend on the DOM being updated, such as network requests or measurements, but only after comparingprevPropsorprevStateto prevent infinite loops. -
Avoid deprecated patterns by moving logic from
componentWillUpdateandcomponentWillReceivePropstocomponentDidUpdate, as enforced by the React test suite increateReactClassIntegration-test.js. -
Pair with
getSnapshotBeforeUpdatewhen you need pre-update DOM information, ensuring the snapshot value is received as the third parameter incomponentDidUpdateper the contract inReactFizzClassComponent.js. -
Never use for initialization or cleanup—reserve
componentDidMountfor one-time setup andcomponentWillUnmountfor 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →