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.
152 lines
4.7 KiB
Markdown
152 lines
4.7 KiB
Markdown
# componentWillUpdate Migration Reference
|
|
|
|
## The Core Decision
|
|
|
|
```
|
|
Does componentWillUpdate read the DOM (scroll, size, position, selection)?
|
|
YES → getSnapshotBeforeUpdate (paired with componentDidUpdate)
|
|
NO (side effects, request cancellation, etc.) → componentDidUpdate
|
|
```
|
|
|
|
---
|
|
|
|
## Case A - Reads DOM Before Re-render {#case-a}
|
|
|
|
The method captures a DOM measurement (scroll position, element size, cursor position) before React applies the next update, so it can be restored or adjusted after.
|
|
|
|
**Before:**
|
|
|
|
```jsx
|
|
class MessageList extends React.Component {
|
|
componentWillUpdate(nextProps) {
|
|
if (nextProps.messages.length > this.props.messages.length) {
|
|
this.savedScrollHeight = this.listRef.current.scrollHeight;
|
|
this.savedScrollTop = this.listRef.current.scrollTop;
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (prevProps.messages.length < this.props.messages.length) {
|
|
const scrollDelta = this.listRef.current.scrollHeight - this.savedScrollHeight;
|
|
this.listRef.current.scrollTop = this.savedScrollTop + scrollDelta;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**After - getSnapshotBeforeUpdate + componentDidUpdate:**
|
|
|
|
```jsx
|
|
class MessageList extends React.Component {
|
|
// Called right before DOM updates are applied - perfect timing to read DOM
|
|
getSnapshotBeforeUpdate(prevProps, prevState) {
|
|
if (prevProps.messages.length < this.props.messages.length) {
|
|
return {
|
|
scrollHeight: this.listRef.current.scrollHeight,
|
|
scrollTop: this.listRef.current.scrollTop,
|
|
};
|
|
}
|
|
return null; // Return null when snapshot is not needed
|
|
}
|
|
|
|
// Receives the snapshot as the third argument
|
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
|
if (snapshot !== null) {
|
|
const scrollDelta = this.listRef.current.scrollHeight - snapshot.scrollHeight;
|
|
this.listRef.current.scrollTop = snapshot.scrollTop + scrollDelta;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Why this is better than componentWillUpdate:** In React 18 concurrent mode, there can be a gap between when `componentWillUpdate` runs and when the DOM actually updates. DOM reads in `componentWillUpdate` may be stale. `getSnapshotBeforeUpdate` runs synchronously right before the DOM is committed - the reads are always accurate.
|
|
|
|
**The contract:**
|
|
|
|
- Return a value from `getSnapshotBeforeUpdate` → that value becomes `snapshot` in `componentDidUpdate`
|
|
- Return `null` → `snapshot` in `componentDidUpdate` is `null`
|
|
- Always check `if (snapshot !== null)` in `componentDidUpdate`
|
|
- `getSnapshotBeforeUpdate` MUST be paired with `componentDidUpdate`
|
|
|
|
---
|
|
|
|
## Case B - Side Effects Before Update {#case-b}
|
|
|
|
The method cancels an in-flight request, clears a timer, or runs some preparatory side effect when props or state are about to change.
|
|
|
|
**Before:**
|
|
|
|
```jsx
|
|
class SearchResults extends React.Component {
|
|
componentWillUpdate(nextProps) {
|
|
if (nextProps.query !== this.props.query) {
|
|
this.currentRequest?.cancel();
|
|
this.setState({ loading: true, results: [] });
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**After - move to componentDidUpdate (run AFTER the update):**
|
|
|
|
```jsx
|
|
class SearchResults extends React.Component {
|
|
componentDidUpdate(prevProps) {
|
|
if (prevProps.query !== this.props.query) {
|
|
// Cancel the stale request
|
|
this.currentRequest?.cancel();
|
|
// Start the new request for the updated query
|
|
this.setState({ loading: true, results: [] });
|
|
this.currentRequest = searchAPI(this.props.query)
|
|
.then(results => this.setState({ results, loading: false }));
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Note:** The side effect now runs AFTER the render, not before. In most cases this is correct - you want to react to the state that's actually showing, not the state that was showing. If you truly need to run something synchronously BEFORE a render, reconsider the design - that usually indicates state that should be managed differently.
|
|
|
|
---
|
|
|
|
## Both Cases in One Component
|
|
|
|
If a component had both DOM-reading AND side effects in `componentWillUpdate`:
|
|
|
|
```jsx
|
|
// Before: does both
|
|
componentWillUpdate(nextProps) {
|
|
// DOM read
|
|
if (isExpanding(nextProps)) {
|
|
this.savedHeight = this.ref.current.offsetHeight;
|
|
}
|
|
// Side effect
|
|
if (nextProps.query !== this.props.query) {
|
|
this.request?.cancel();
|
|
}
|
|
}
|
|
```
|
|
|
|
After: split into both patterns:
|
|
|
|
```jsx
|
|
// DOM read → getSnapshotBeforeUpdate
|
|
getSnapshotBeforeUpdate(prevProps, prevState) {
|
|
if (isExpanding(this.props)) {
|
|
return { height: this.ref.current.offsetHeight };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Side effect → componentDidUpdate
|
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
|
// Handle snapshot if present
|
|
if (snapshot !== null) { /* ... */ }
|
|
|
|
// Handle side effect
|
|
if (prevProps.query !== this.props.query) {
|
|
this.request?.cancel();
|
|
this.startNewRequest();
|
|
}
|
|
}
|
|
```
|