Generated banner for React18 String Refs
Generated logo for React18 String Refs

React18 String Refs

GitHubOfficialFirst committed by SaravananRajaramanDistributed by GitHub

  • Skill
  • Guidelines
  • Software development
  • MIT
  • No issues found

From the author

Provides exact migration patterns for React string refs (ref="name" + this.refs.name) to React.createRef() in class components. Use this skill whenever migrating string ref usage - including single element refs, multiple refs in a component, refs in lists, callback refs, and refs passed to child components. Always use this skill before writing any ref migration code - the multiple-refs-in-list pattern is particularly tricky and this skill prevents the most common mistakes. Use it for React 18.3.1 migration (string refs warn) and React 19 migration (string refs removed).
— the description by SaravananRajaraman

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

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/github/react18-string-refs/download
  2. Unzip 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

PatternReference
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():

  1. Add refName = React.createRef(); as a class field (or in constructor)

  2. Replace ref="refName" → ref={this.refName} in JSX

  3. Replace this.refs.refName → this.refName.current everywhere

Read references/patterns.md for the full before/after for each case.

Files

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].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.

// 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
GitHub avatar of SaravananRajaramanSaravananRajaraman
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