mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-11 18:55:55 +00:00
- Adds React 18 and 19 migration orchestration plugins - Introduces comprehensive upgrade toolkits for migrating legacy React 16/17 and 18 codebases to React 18.3.1 and 19, respectively. Each plugin bundles specialized agents and skills for exhaustive audit, dependency management, class/component API migration, test suite transformation, and batching regression fixes. - The React 18 toolkit targets class-component-heavy apps, ensures safe lifecycle and context transitions, resolves dependency blockers, and fully automates test migrations including Enzyme removal. The React 19 toolkit addresses breaking changes such as removal of legacy APIs, defaultProps on function components, and forwardRef, while enforcing a gated, memory-resumable migration pipeline. - Both plugins update documentation, plugin registries, and skill references to support reliable, repeatable enterprise-scale React migrations.
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.
|