React18 String Refs
GitHubOfficialFirst committed by SaravananRajaramanDistributed by GitHub
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a comprehensive guide for migrating deprecated string refs (ref='name' and this.refs.name) to the modern React.createRef() API. It covers various scenarios including single elements, multiple refs, and complex dynamic refs in lists, ensuring code remains compatible with React 18.3.1 and React 19, where string refs are removed.
What it does
- Provides a clear mapping of legacy string ref patterns to modern React.createRef() implementations.
- Offers specific strategies for handling dynamic refs within lists using Maps or callback refs.
- Includes command-line instructions to identify legacy ref usage in existing codebases.
- Details the step-by-step process for updating class component ref declarations and accessors.
- Addresses edge cases like refs passed to child components.
When to use it
- When upgrading a React application to version 18.3.1 or 19.
- When refactoring class components to remove deprecated string ref syntax.
- When encountering warnings about string refs in the console during development.
- When needing a reliable pattern for managing dynamic refs in loops or maps.
Advantages
- Prevents common pitfalls associated with dynamic ref management in lists.
- Ensures future-proof code by aligning with React 19 requirements.
- Provides consistent, tested patterns for various ref scenarios.
- Reduces manual effort by offering a clear migration path for both declarations and accessors.
Usage examples
- “How do I migrate a simple string ref in a class component?”
- The skill instructs to replace the string ref with a React.createRef() class field and update all this.refs.name references to this.refName.current.
- “What is the best way to handle dynamic refs in a list?”
- The skill provides a pattern using a Map to store refs dynamically or an alternative callback ref approach to store DOM nodes directly.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/github/react18-string-refs/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip react18-string-refs.zip -d .agents/skills
Documentation by SaravananRajaraman
React 18 String Refs Migration
String refs (ref="myInput" + this.refs.myInput) were deprecated in React 16.3, warn in React 18.3.1, and are removed in React 19.
Quick Pattern Map
| Pattern | Reference |
|---|---|
| Single ref on a DOM element | → patterns.md#single-ref |
| Multiple refs in one component | → patterns.md#multiple-refs |
| Refs in a list / dynamic refs | → patterns.md#list-refs |
| Callback refs (alternative approach) | → patterns.md#callback-refs |
| Ref passed to a child component | → patterns.md#forwarded-refs |
Scan Command
# Find all string ref assignments in JSX
grep -rn 'ref="' src/ --include="*.js" --include="*.jsx" | grep -v "\.test\."
# Find all this.refs accessors
grep -rn "this\.refs\." src/ --include="*.js" --include="*.jsx" | grep -v "\.test\."Both should be migrated together - find the ref="name" and the this.refs.name accesses for each component as a pair.
The Migration Rule
Every string ref migrates to React.createRef():
Add
refName = React.createRef();as a class field (or in constructor)Replace
ref="refName"→ref={this.refName}in JSXReplace
this.refs.refName→this.refName.currenteverywhere
Read references/patterns.md for the full before/after for each case.
Files
- ATTRIBUTION.mdAdded by AutoGineer891 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md2.1 KiB
- references/patterns.md6.6 KiB
references/patterns.md
String Refs - All Migration Patterns
Single Ref on a DOM Element {#single-ref}
The most common case - one ref to one DOM node.
// 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>
);
}
}// 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.
// 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>
);
}
}// 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.
// 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>
);
}
}// 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):
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].currentCallback 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.
// 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:
// 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.
// 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" />;
}
}// 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" />;
}Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
SaravananRajaraman
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/react18-string-refs · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- SaravananRajaraman
AutoGineer is not affiliated with or endorsed by SaravananRajaraman or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download react18-string-refs.zip