componentDidUpdate fires after every update, a re-render triggered by changed props or state, but critically not after the initial mount, which componentDidMount handles instead. It receives the previous props and previous state as arguments, letting you compare them against this.props/this.state, the current values, to decide whether specific new work, like re-fetching data because a particular prop actually changed, is genuinely needed — this comparison is essential, since without it, code inside componentDidUpdate that unconditionally calls setState or fetches data would run on every single update and risk creating an infinite update loop.
1Understanding ComponentDidUpdate
componentDidUpdate fires after every update, a re-render triggered by changed props or state, but critically not after the initial mount, which componentDidMount handles instead. It receives the previous props and previous state as arguments, letting you compare them against this.props/this.state, the current values, to decide whether specific new work, like re-fetching data because a particular prop actually changed, is genuinely needed — this comparison is essential, since without it, code inside componentDidUpdate that unconditionally calls setState or fetches data would run on every single update and risk creating an infinite update loop.
Always compare the relevant prevProps/prevState value against the current one inside componentDidUpdate before performing conditional work like a data fetch — skipping this check and unconditionally calling setState() inside componentDidUpdate is a classic way to create an infinite re-render loop.
class UserProfile extends React.Component {
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
console.log('userId changed, refetching...');
fetchUser(this.props.userId).then(user => this.setState({ name: user.name }));
}
}
render() {
return <p>{this.state?.name ?? 'Loading...'}</p>;
}
}2Practical Example
Here is a real-world application of ComponentDidUpdate showing how it is used in production React code.
class BadCounter extends React.Component {
state = { count: 0 };
componentDidUpdate() {
this.setState({ count: this.state.count + 1 }); // BUG: unconditional setState
}
render() {
return <p>{this.state.count}</p>;
}
}3Best Practices
Follow these guidelines when working with ComponentDidUpdate:
1. Compare the relevant prevProps or prevState value against the current value before triggering conditional work like a fetch, to avoid running it on every single update
2. Never call setState() unconditionally inside componentDidUpdate, since that state change itself triggers another update, another componentDidUpdate call, and potentially an infinite loop
3. Use the functional-component equivalent, useEffect(fn, [dependency]), when writing new code, reserving componentDidUpdate for maintaining existing class-based components
Tip: Always compare the relevant prevProps/prevState value against the current one inside componentDidUpdate before performing conditional work like a data fetch — skipping this check and unconditionally calling setState() inside componentDidUpdate is a classic way to create an infinite re-render loop.
class UserProfile extends React.Component {
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
console.log('userId changed, refetching...');
fetchUser(this.props.userId).then(user => this.setState({ name: user.name }));
}
}
render() {
return <p>{this.state?.name ?? 'Loading...'}</p>;
}
}