React is warning that string-based refs (e.g., ref="myInput") are deprecated. String refs have multiple downsides including poor composability, debugging issues, and will be removed in React 19. You need to migrate to useRef, createRef, or callback refs.
This warning appears when you're using the legacy string ref syntax in React components. String refs were the original way to reference DOM elements or component instances in React, but they've been deprecated since React 16.3 (March 2018) and are being completely removed in React 19. String refs look like this: `<input ref="myInput" />` and are accessed via `this.refs.myInput`. While they seem simple, they have several fundamental problems: - **Not composable**: If a wrapper component also uses a string ref with the same name, they overwrite each other - **Poor debugging**: Errors are harder to trace when refs collide - **No TypeScript support**: String refs don't work well with static type checking - **Framework complexity**: They require React to maintain a special refs object on every component instance React's core team deprecated string refs in favor of callback refs, createRef, and the useRef hook, which provide better type safety, composability, and debugging capabilities.
Search your project for string ref usage:
# Search for string refs in your codebase
grep -r 'ref="' src/
grep -r "ref='" src/
grep -r 'this.refs' src/Common patterns to look for:
- ref="inputRef"
- this.refs.inputRef
- this.refs['inputRef']
For function components, replace string refs with the useRef hook:
Before (string ref - deprecated):
function MyComponent() {
const handleClick = () => {
// This won't work with function components
this.refs.inputRef.focus();
};
return <input ref="inputRef" />;
}After (useRef hook - recommended):
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current?.focus();
};
return <input ref={inputRef} />;
}The useRef hook creates a mutable ref object with a .current property that persists across re-renders.
For class components, use React.createRef() in the constructor:
Before (string ref - deprecated):
class MyComponent extends React.Component {
handleClick = () => {
this.refs.inputRef.focus();
}
render() {
return <input ref="inputRef" />;
}
}After (createRef - recommended):
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
handleClick = () => {
this.inputRef.current?.focus();
}
render() {
return <input ref={this.inputRef} />;
}
}Create the ref in the constructor and access it via .current.
For more control over when refs are set or unset, use callback refs:
function MyComponent() {
const [inputElement, setInputElement] = React.useState(null);
const handleRef = (element) => {
setInputElement(element);
// You can perform actions when the ref is set
if (element) {
console.log('Input mounted:', element);
}
};
return <input ref={handleRef} />;
}Or in class components:
class MyComponent extends React.Component {
inputRef = null;
setRef = (element) => {
this.inputRef = element;
}
render() {
return <input ref={this.setRef} />;
}
}Callback refs are useful when you need to perform side effects when a ref is attached or detached.
If the warning comes from a third-party library, check for updates:
# Check for outdated packages
npm outdated
# Update specific package
npm update package-name
# Or update to latest version
npm install package-name@latestIf the library hasn't been updated:
- Check the library's GitHub issues for migration plans
- Consider switching to an actively maintained alternative
- File an issue requesting React 19 compatibility
- Fork and patch if necessary (last resort)
After migrating refs, test all affected functionality:
// Example test with React Testing Library
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('input ref works correctly', () => {
render(<MyComponent />);
const input = screen.getByRole('textbox');
// Verify ref-based operations work
expect(document.activeElement).not.toBe(input);
userEvent.click(screen.getByText('Focus Input'));
expect(document.activeElement).toBe(input);
});Test scenarios:
- Focus management
- Scroll positioning
- DOM measurements
- Third-party library integrations
- Animation triggers
## When to use each ref approach
useRef (function components):
- Default choice for function components
- Simple DOM access
- Storing mutable values without triggering re-renders
- Persists across re-renders
createRef (class components):
- Standard approach for class components
- Created in constructor
- Attached to instance properties
Callback refs:
- When you need notification when ref attaches/detaches
- Dynamic refs (refs in lists or conditional rendering)
- Synchronizing refs with state
- Measuring DOM nodes immediately after mounting
- Integrating with non-React code
## React 19 changes
React 19 completely removes string refs. If you're upgrading:
1. Upgrade to React 18.3 first - it adds warnings for all deprecated APIs
2. Fix all string ref warnings
3. Update dependencies to React 19-compatible versions
4. Then upgrade to React 19
React 19 also introduces ref as a prop on function components, eliminating the need for forwardRef in many cases:
// React 19+
function MyInput({ ref }) {
return <input ref={ref} />;
}## Performance considerations
Avoid inline callback refs in hot render paths:
// ❌ Avoid: Creates new function every render
<input ref={(el) => this.inputRef = el} />
// ✅ Better: Stable function reference
<input ref={this.setInputRef} />Inline callback refs are called twice during updates (first with null, then with the element), which can cause unnecessary work.
## TypeScript support
Modern refs have excellent TypeScript support:
import { useRef } from 'react';
function MyComponent() {
// Type is inferred from initial value
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
// TypeScript knows inputRef.current might be null
inputRef.current?.focus();
};
return <input ref={inputRef} />;
}String refs have no TypeScript support, making them unsuitable for typed codebases.
React Hook useCallback has a missing dependency: 'variable'. Either include it or remove the dependency array react-hooks/exhaustive-deps
React Hook useCallback has a missing dependency
Cannot use private fields in class components without TS support
Cannot use private fields in class components without TS support
Cannot destructure property 'xxx' of 'undefined'
Cannot destructure property of undefined when accessing props
useNavigate() may be used only in the context of a <Router> component.
useNavigate() may be used only in the context of a Router component
Cannot find module or its corresponding type declarations
How to fix "Cannot find module or type declarations" in Vite