mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-11 02:35: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.
302 lines
6.6 KiB
Markdown
302 lines
6.6 KiB
Markdown
# String Refs - All Migration Patterns
|
|
|
|
## Single Ref on a DOM Element {#single-ref}
|
|
|
|
The most common case - one ref to one DOM node.
|
|
|
|
```jsx
|
|
// Before:
|
|
class SearchBox extends React.Component {
|
|
handleSearch() {
|
|
const value = this.refs.searchInput.value;
|
|
this.props.onSearch(value);
|
|
}
|
|
|
|
focusInput() {
|
|
this.refs.searchInput.focus();
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
<input ref="searchInput" type="text" placeholder="Search..." />
|
|
<button onClick={() => this.handleSearch()}>Search</button>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
```jsx
|
|
// After:
|
|
class SearchBox extends React.Component {
|
|
searchInputRef = React.createRef();
|
|
|
|
handleSearch() {
|
|
const value = this.searchInputRef.current.value;
|
|
this.props.onSearch(value);
|
|
}
|
|
|
|
focusInput() {
|
|
this.searchInputRef.current.focus();
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
<input ref={this.searchInputRef} type="text" placeholder="Search..." />
|
|
<button onClick={() => this.handleSearch()}>Search</button>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Multiple Refs in One Component {#multiple-refs}
|
|
|
|
Each string ref becomes its own named `createRef()` field.
|
|
|
|
```jsx
|
|
// Before:
|
|
class LoginForm extends React.Component {
|
|
handleSubmit(e) {
|
|
e.preventDefault();
|
|
const email = this.refs.emailField.value;
|
|
const password = this.refs.passwordField.value;
|
|
this.props.onSubmit({ email, password });
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<form onSubmit={this.handleSubmit}>
|
|
<input ref="emailField" type="email" />
|
|
<input ref="passwordField" type="password" />
|
|
<button type="submit">Log in</button>
|
|
</form>
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
```jsx
|
|
// After:
|
|
class LoginForm extends React.Component {
|
|
emailFieldRef = React.createRef();
|
|
passwordFieldRef = React.createRef();
|
|
|
|
handleSubmit(e) {
|
|
e.preventDefault();
|
|
const email = this.emailFieldRef.current.value;
|
|
const password = this.passwordFieldRef.current.value;
|
|
this.props.onSubmit({ email, password });
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<form onSubmit={this.handleSubmit}>
|
|
<input ref={this.emailFieldRef} type="email" />
|
|
<input ref={this.passwordFieldRef} type="password" />
|
|
<button type="submit">Log in</button>
|
|
</form>
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Refs in a List / Dynamic Refs {#list-refs}
|
|
|
|
String refs in a map/loop - the most tricky case. Each item needs its own ref.
|
|
|
|
```jsx
|
|
// Before:
|
|
class TabPanel extends React.Component {
|
|
focusTab(index) {
|
|
this.refs[`tab_${index}`].focus();
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
{this.props.tabs.map((tab, i) => (
|
|
<button key={tab.id} ref={`tab_${i}`}>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
```jsx
|
|
// After - use a Map to store refs dynamically:
|
|
class TabPanel extends React.Component {
|
|
tabRefs = new Map();
|
|
|
|
getOrCreateRef(id) {
|
|
if (!this.tabRefs.has(id)) {
|
|
this.tabRefs.set(id, React.createRef());
|
|
}
|
|
return this.tabRefs.get(id);
|
|
}
|
|
|
|
focusTab(index) {
|
|
const tab = this.props.tabs[index];
|
|
this.tabRefs.get(tab.id)?.current?.focus();
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
{this.props.tabs.map((tab) => (
|
|
<button key={tab.id} ref={this.getOrCreateRef(tab.id)}>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
**Alternative - callback ref for lists (simpler):**
|
|
|
|
```jsx
|
|
class TabPanel extends React.Component {
|
|
tabRefs = {};
|
|
|
|
focusTab(index) {
|
|
this.tabRefs[index]?.focus();
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
{this.props.tabs.map((tab, i) => (
|
|
<button
|
|
key={tab.id}
|
|
ref={el => { this.tabRefs[i] = el; }} // callback ref stores DOM node directly
|
|
>
|
|
{tab.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
// Note: callback refs store the DOM node directly (not wrapped in .current)
|
|
// this.tabRefs[i] is the element, not this.tabRefs[i].current
|
|
```
|
|
|
|
---
|
|
|
|
## Callback Refs (Alternative to createRef) {#callback-refs}
|
|
|
|
Callback refs are an alternative to `createRef()`. They're useful for lists (above) and when you need to run code when the ref attaches/detaches.
|
|
|
|
```jsx
|
|
// Callback ref syntax:
|
|
class MyComponent extends React.Component {
|
|
// Callback ref - called with the element when it mounts, null when it unmounts
|
|
setInputRef = (el) => {
|
|
this.inputEl = el; // stores the DOM node directly (no .current needed)
|
|
};
|
|
|
|
focusInput() {
|
|
this.inputEl?.focus(); // direct DOM node access
|
|
}
|
|
|
|
render() {
|
|
return <input ref={this.setInputRef} />;
|
|
}
|
|
}
|
|
```
|
|
|
|
**When to use callback refs vs createRef:**
|
|
|
|
- `createRef()` - for a fixed number of refs known at component definition time (most cases)
|
|
- Callback refs - for dynamic lists, when you need to react to attach/detach, or when the ref might change
|
|
|
|
**Important:** Inline callback refs (defined in render) re-create a new function on every render, which causes the ref to be called with `null` then the element on each render cycle. Use a bound method or class field arrow function instead:
|
|
|
|
```jsx
|
|
// AVOID - new function every render, causes ref flicker:
|
|
render() {
|
|
return <input ref={(el) => { this.inputEl = el; }} />; // inline - bad
|
|
}
|
|
|
|
// PREFER - stable reference:
|
|
setInputRef = (el) => { this.inputEl = el; }; // class field - good
|
|
render() {
|
|
return <input ref={this.setInputRef} />;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Ref Passed to a Child Component {#forwarded-refs}
|
|
|
|
If a string ref was passed to a custom component (not a DOM element), the migration also requires updating the child.
|
|
|
|
```jsx
|
|
// Before:
|
|
class Parent extends React.Component {
|
|
handleClick() {
|
|
this.refs.myInput.focus(); // Parent accesses child's DOM node
|
|
}
|
|
render() {
|
|
return (
|
|
<div>
|
|
<MyInput ref="myInput" />
|
|
<button onClick={() => this.handleClick()}>Focus</button>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
// MyInput.js (child - class component):
|
|
class MyInput extends React.Component {
|
|
render() {
|
|
return <input className="my-input" />;
|
|
}
|
|
}
|
|
```
|
|
|
|
```jsx
|
|
// After:
|
|
class Parent extends React.Component {
|
|
myInputRef = React.createRef();
|
|
|
|
handleClick() {
|
|
this.myInputRef.current.focus();
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<div>
|
|
{/* React 18: forwardRef needed. React 19: ref is a direct prop */}
|
|
<MyInput ref={this.myInputRef} />
|
|
<button onClick={() => this.handleClick()}>Focus</button>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
// MyInput.js (React 18 - use forwardRef):
|
|
import { forwardRef } from 'react';
|
|
const MyInput = forwardRef(function MyInput(props, ref) {
|
|
return <input ref={ref} className="my-input" />;
|
|
});
|
|
|
|
// MyInput.js (React 19 - ref as direct prop, no forwardRef):
|
|
function MyInput({ ref, ...props }) {
|
|
return <input ref={ref} className="my-input" />;
|
|
}
|
|
```
|
|
|
|
---
|