{"kind":"Skill","metadata":{"namespace":"community","name":"react18-string-refs","version":"0.1.0"},"spec":{"description":"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).","files":{"SKILL.md":"---\nname: react18-string-refs\ndescription: '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).'\n---\n\n# React 18 String Refs Migration\n\nString refs (`ref=\"myInput\"` + `this.refs.myInput`) were deprecated in React 16.3, warn in React 18.3.1, and are **removed in React 19**.\n\n## Quick Pattern Map\n\n| Pattern | Reference |\n|---|---|\n| Single ref on a DOM element | [→ patterns.md#single-ref](references/patterns.md#single-ref) |\n| Multiple refs in one component | [→ patterns.md#multiple-refs](references/patterns.md#multiple-refs) |\n| Refs in a list / dynamic refs | [→ patterns.md#list-refs](references/patterns.md#list-refs) |\n| Callback refs (alternative approach) | [→ patterns.md#callback-refs](references/patterns.md#callback-refs) |\n| Ref passed to a child component | [→ patterns.md#forwarded-refs](references/patterns.md#forwarded-refs) |\n\n## Scan Command\n\n```bash\n# Find all string ref assignments in JSX\ngrep -rn 'ref=\"' src/ --include=\"*.js\" --include=\"*.jsx\" | grep -v \"\\.test\\.\"\n\n# Find all this.refs accessors\ngrep -rn \"this\\.refs\\.\" src/ --include=\"*.js\" --include=\"*.jsx\" | grep -v \"\\.test\\.\"\n```\n\nBoth should be migrated together - find the `ref=\"name\"` and the `this.refs.name` accesses for each component as a pair.\n\n## The Migration Rule\n\nEvery string ref migrates to `React.createRef()`:\n\n1. Add `refName = React.createRef();` as a class field (or in constructor)\n2. Replace `ref=\"refName\"` → `ref={this.refName}` in JSX\n3. Replace `this.refs.refName` → `this.refName.current` everywhere\n\nRead `references/patterns.md` for the full before/after for each case.\n","references/patterns.md":"# String Refs - All Migration Patterns\n\n## Single Ref on a DOM Element {#single-ref}\n\nThe most common case - one ref to one DOM node.\n\n```jsx\n// Before:\nclass SearchBox extends React.Component {\n  handleSearch() {\n    const value = this.refs.searchInput.value;\n    this.props.onSearch(value);\n  }\n\n  focusInput() {\n    this.refs.searchInput.focus();\n  }\n\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cinput ref=\"searchInput\" type=\"text\" placeholder=\"Search...\" /\u003e\n        \u003cbutton onClick={() =\u003e this.handleSearch()}\u003eSearch\u003c/button\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n```\n\n```jsx\n// After:\nclass SearchBox extends React.Component {\n  searchInputRef = React.createRef();\n\n  handleSearch() {\n    const value = this.searchInputRef.current.value;\n    this.props.onSearch(value);\n  }\n\n  focusInput() {\n    this.searchInputRef.current.focus();\n  }\n\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cinput ref={this.searchInputRef} type=\"text\" placeholder=\"Search...\" /\u003e\n        \u003cbutton onClick={() =\u003e this.handleSearch()}\u003eSearch\u003c/button\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n```\n\n---\n\n## Multiple Refs in One Component {#multiple-refs}\n\nEach string ref becomes its own named `createRef()` field.\n\n```jsx\n// Before:\nclass LoginForm extends React.Component {\n  handleSubmit(e) {\n    e.preventDefault();\n    const email = this.refs.emailField.value;\n    const password = this.refs.passwordField.value;\n    this.props.onSubmit({ email, password });\n  }\n\n  render() {\n    return (\n      \u003cform onSubmit={this.handleSubmit}\u003e\n        \u003cinput ref=\"emailField\" type=\"email\" /\u003e\n        \u003cinput ref=\"passwordField\" type=\"password\" /\u003e\n        \u003cbutton type=\"submit\"\u003eLog in\u003c/button\u003e\n      \u003c/form\u003e\n    );\n  }\n}\n```\n\n```jsx\n// After:\nclass LoginForm extends React.Component {\n  emailFieldRef = React.createRef();\n  passwordFieldRef = React.createRef();\n\n  handleSubmit(e) {\n    e.preventDefault();\n    const email = this.emailFieldRef.current.value;\n    const password = this.passwordFieldRef.current.value;\n    this.props.onSubmit({ email, password });\n  }\n\n  render() {\n    return (\n      \u003cform onSubmit={this.handleSubmit}\u003e\n        \u003cinput ref={this.emailFieldRef} type=\"email\" /\u003e\n        \u003cinput ref={this.passwordFieldRef} type=\"password\" /\u003e\n        \u003cbutton type=\"submit\"\u003eLog in\u003c/button\u003e\n      \u003c/form\u003e\n    );\n  }\n}\n```\n\n---\n\n## Refs in a List / Dynamic Refs {#list-refs}\n\nString refs in a map/loop - the most tricky case. Each item needs its own ref.\n\n```jsx\n// Before:\nclass TabPanel extends React.Component {\n  focusTab(index) {\n    this.refs[`tab_${index}`].focus();\n  }\n\n  render() {\n    return (\n      \u003cdiv\u003e\n        {this.props.tabs.map((tab, i) =\u003e (\n          \u003cbutton key={tab.id} ref={`tab_${i}`}\u003e\n            {tab.label}\n          \u003c/button\u003e\n        ))}\n      \u003c/div\u003e\n    );\n  }\n}\n```\n\n```jsx\n// After - use a Map to store refs dynamically:\nclass TabPanel extends React.Component {\n  tabRefs = new Map();\n\n  getOrCreateRef(id) {\n    if (!this.tabRefs.has(id)) {\n      this.tabRefs.set(id, React.createRef());\n    }\n    return this.tabRefs.get(id);\n  }\n\n  focusTab(index) {\n    const tab = this.props.tabs[index];\n    this.tabRefs.get(tab.id)?.current?.focus();\n  }\n\n  render() {\n    return (\n      \u003cdiv\u003e\n        {this.props.tabs.map((tab) =\u003e (\n          \u003cbutton key={tab.id} ref={this.getOrCreateRef(tab.id)}\u003e\n            {tab.label}\n          \u003c/button\u003e\n        ))}\n      \u003c/div\u003e\n    );\n  }\n}\n```\n\n**Alternative - callback ref for lists (simpler):**\n\n```jsx\nclass TabPanel extends React.Component {\n  tabRefs = {};\n\n  focusTab(index) {\n    this.tabRefs[index]?.focus();\n  }\n\n  render() {\n    return (\n      \u003cdiv\u003e\n        {this.props.tabs.map((tab, i) =\u003e (\n          \u003cbutton\n            key={tab.id}\n            ref={el =\u003e { this.tabRefs[i] = el; }}  // callback ref stores DOM node directly\n          \u003e\n            {tab.label}\n          \u003c/button\u003e\n        ))}\n      \u003c/div\u003e\n    );\n  }\n}\n// Note: callback refs store the DOM node directly (not wrapped in .current)\n// this.tabRefs[i] is the element, not this.tabRefs[i].current\n```\n\n---\n\n## Callback Refs (Alternative to createRef) {#callback-refs}\n\nCallback refs are an alternative to `createRef()`. They're useful for lists (above) and when you need to run code when the ref attaches/detaches.\n\n```jsx\n// Callback ref syntax:\nclass MyComponent extends React.Component {\n  // Callback ref - called with the element when it mounts, null when it unmounts\n  setInputRef = (el) =\u003e {\n    this.inputEl = el; // stores the DOM node directly (no .current needed)\n  };\n\n  focusInput() {\n    this.inputEl?.focus(); // direct DOM node access\n  }\n\n  render() {\n    return \u003cinput ref={this.setInputRef} /\u003e;\n  }\n}\n```\n\n**When to use callback refs vs createRef:**\n\n- `createRef()` - for a fixed number of refs known at component definition time (most cases)\n- Callback refs - for dynamic lists, when you need to react to attach/detach, or when the ref might change\n\n**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:\n\n```jsx\n// AVOID - new function every render, causes ref flicker:\nrender() {\n  return \u003cinput ref={(el) =\u003e { this.inputEl = el; }} /\u003e;  // inline - bad\n}\n\n// PREFER - stable reference:\nsetInputRef = (el) =\u003e { this.inputEl = el; };  // class field - good\nrender() {\n  return \u003cinput ref={this.setInputRef} /\u003e;\n}\n```\n\n---\n\n## Ref Passed to a Child Component {#forwarded-refs}\n\nIf a string ref was passed to a custom component (not a DOM element), the migration also requires updating the child.\n\n```jsx\n// Before:\nclass Parent extends React.Component {\n  handleClick() {\n    this.refs.myInput.focus(); // Parent accesses child's DOM node\n  }\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cMyInput ref=\"myInput\" /\u003e\n        \u003cbutton onClick={() =\u003e this.handleClick()}\u003eFocus\u003c/button\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\n// MyInput.js (child - class component):\nclass MyInput extends React.Component {\n  render() {\n    return \u003cinput className=\"my-input\" /\u003e;\n  }\n}\n```\n\n```jsx\n// After:\nclass Parent extends React.Component {\n  myInputRef = React.createRef();\n\n  handleClick() {\n    this.myInputRef.current.focus();\n  }\n\n  render() {\n    return (\n      \u003cdiv\u003e\n        {/* React 18: forwardRef needed. React 19: ref is a direct prop */}\n        \u003cMyInput ref={this.myInputRef} /\u003e\n        \u003cbutton onClick={() =\u003e this.handleClick()}\u003eFocus\u003c/button\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\n// MyInput.js (React 18 - use forwardRef):\nimport { forwardRef } from 'react';\nconst MyInput = forwardRef(function MyInput(props, ref) {\n  return \u003cinput ref={ref} className=\"my-input\" /\u003e;\n});\n\n// MyInput.js (React 19 - ref as direct prop, no forwardRef):\nfunction MyInput({ ref, ...props }) {\n  return \u003cinput ref={ref} className=\"my-input\" /\u003e;\n}\n```\n\n---\n"},"import":{"commit_sha":"541b7819d8c3545c6df122491af4fa1eae415779","imported_at":"2026-05-18T20:05:35Z","license_text":"MIT License\n\nCopyright GitHub, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","owner":"github","repo":"github/awesome-copilot","source_url":"https://github.com/github/awesome-copilot/tree/541b7819d8c3545c6df122491af4fa1eae415779/plugins/react18-upgrade/skills/react18-string-refs"}},"content_hash":[92,137,3,102,191,75,58,126,57,24,202,142,220,249,209,38,118,127,180,230,202,121,98,42,14,89,105,71,121,88,78,209],"trust_level":"unsigned","yanked":false}
