mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-11 18:55:55 +00:00
165 lines
5.0 KiB
Markdown
165 lines
5.0 KiB
Markdown
# 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 {#case-a}
|
|
|
|
The method fetches data, cancels requests, updates external state, or runs any async operation when a prop changes.
|
|
|
|
**Before:**
|
|
|
|
```jsx
|
|
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:**
|
|
|
|
```jsx
|
|
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):
|
|
|
|
```jsx
|
|
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 {#case-b}
|
|
|
|
The method only derives state values from the new props synchronously. No async work, no side effects, no external calls.
|
|
|
|
**Before:**
|
|
|
|
```jsx
|
|
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:**
|
|
|
|
```jsx
|
|
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.
|
|
|
|
```jsx
|
|
// 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.
|
|
|
|
```jsx
|
|
// 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.
|