5.0 KiB
componentWillReceiveProps Migration Reference
The Core Decision
Does componentWillReceiveProps trigger async work or side effects?
YES → componentDidUpdate
NO (pure state derivation only) → getDerivedStateFromProps
When in doubt: use componentDidUpdate. It's always safe.
getDerivedStateFromProps has traps (see bottom of this file) that make it the wrong choice when the logic is anything other than purely synchronous state derivation.
Case A - Async Side Effects / Fetch on Prop Change
The method fetches data, cancels requests, updates external state, or runs any async operation when a prop changes.
Before:
class UserProfile extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.userId !== this.props.userId) {
this.setState({ loading: true, profile: null });
fetchProfile(nextProps.userId)
.then(profile => this.setState({ profile, loading: false }))
.catch(err => this.setState({ error: err, loading: false }));
}
}
}
After - componentDidUpdate:
class UserProfile extends React.Component {
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
// Use this.props (not nextProps - the update already happened)
this.setState({ loading: true, profile: null });
fetchProfile(this.props.userId)
.then(profile => this.setState({ profile, loading: false }))
.catch(err => this.setState({ error: err, loading: false }));
}
}
}
Key difference: componentDidUpdate receives prevProps - you compare prevProps.x !== this.props.x instead of this.props.x !== nextProps.x. The update has already applied.
Cancellation pattern (important for async):
class UserProfile extends React.Component {
_requestId = 0;
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
const requestId = ++this._requestId;
this.setState({ loading: true });
fetchProfile(this.props.userId).then(profile => {
// Ignore stale responses if userId changed again
if (requestId === this._requestId) {
this.setState({ profile, loading: false });
}
});
}
}
}
Case B - Pure State Derivation from Props
The method only derives state values from the new props synchronously. No async work, no side effects, no external calls.
Before:
class SortedList extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.items !== this.props.items) {
this.setState({
sortedItems: [...nextProps.items].sort((a, b) => a.name.localeCompare(b.name)),
});
}
}
}
After - getDerivedStateFromProps:
class SortedList extends React.Component {
// Must track previous prop to detect changes
static getDerivedStateFromProps(props, state) {
if (props.items !== state.prevItems) {
return {
sortedItems: [...props.items].sort((a, b) => a.name.localeCompare(b.name)),
prevItems: props.items, // ← always store the prop you're comparing
};
}
return null; // null = no state change
}
constructor(props) {
super(props);
this.state = {
sortedItems: [...props.items].sort((a, b) => a.name.localeCompare(b.name)),
prevItems: props.items, // ← initialize in constructor too
};
}
}
getDerivedStateFromProps - Traps and Warnings
Trap 1: It fires on EVERY render, not just prop changes
Unlike componentWillReceiveProps, getDerivedStateFromProps is called before every render - including setState calls. Always compare against previous values stored in state.
// WRONG - fires on every render, including setState triggers
static getDerivedStateFromProps(props, state) {
return { sortedItems: sort(props.items) }; // re-sorts on every setState!
}
// CORRECT - only updates when items reference changes
static getDerivedStateFromProps(props, state) {
if (props.items !== state.prevItems) {
return { sortedItems: sort(props.items), prevItems: props.items };
}
return null;
}
Trap 2: It cannot access this
getDerivedStateFromProps is a static method. No this.props, no this.state, no instance methods.
// WRONG - no this in static method
static getDerivedStateFromProps(props, state) {
return { value: this.computeValue(props) }; // ReferenceError
}
// CORRECT - pure function of props + state
static getDerivedStateFromProps(props, state) {
return { value: computeValue(props) }; // standalone function
}
Trap 3: Don't use it for side effects
If you need to fetch when a prop changes - use componentDidUpdate. getDerivedStateFromProps must be pure.
When getDerivedStateFromProps is actually the wrong tool
If you find yourself doing complex logic in getDerivedStateFromProps, consider whether the consuming component should receive pre-processed data as a prop instead. The pattern exists for narrow use cases, not general prop-to-state syncing.