# 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 (
); } } ``` ```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 (
); } } ``` --- ## 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 (
); } } ``` ```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 (
); } } ``` --- ## 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 (
{this.props.tabs.map((tab, i) => ( ))}
); } } ``` ```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 (
{this.props.tabs.map((tab) => ( ))}
); } } ``` **Alternative - callback ref for lists (simpler):** ```jsx class TabPanel extends React.Component { tabRefs = {}; focusTab(index) { this.tabRefs[index]?.focus(); } render() { return (
{this.props.tabs.map((tab, i) => ( ))}
); } } // 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 ; } } ``` **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 { this.inputEl = el; }} />; // inline - bad } // PREFER - stable reference: setInputRef = (el) => { this.inputEl = el; }; // class field - good render() { return ; } ``` --- ## 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 (
); } } // MyInput.js (child - class component): class MyInput extends React.Component { render() { return ; } } ``` ```jsx // After: class Parent extends React.Component { myInputRef = React.createRef(); handleClick() { this.myInputRef.current.focus(); } render() { return (
{/* React 18: forwardRef needed. React 19: ref is a direct prop */}
); } } // MyInput.js (React 18 - use forwardRef): import { forwardRef } from 'react'; const MyInput = forwardRef(function MyInput(props, ref) { return ; }); // MyInput.js (React 19 - ref as direct prop, no forwardRef): function MyInput({ ref, ...props }) { return ; } ``` ---