The Error
You attached a ref to a custom component and got this in the console:
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Your code probably looks something like this:
function TextInput({ placeholder }) {
return <input placeholder={placeholder} />;
}
// Somewhere else
const inputRef = useRef(null);
<TextInput ref={inputRef} placeholder="Type here" />
The ref silently does nothing. inputRef.current stays null, React logs that warning, and nothing breaks loudly โ which makes this bug easy to miss.
Why This Happens
ref is not a regular prop in React. Write ref={inputRef} on a function component and React intercepts it before it ever reaches your component โ it never shows up in props. Since plain function components have no instance, there's nowhere to attach the ref, so React drops it entirely.
Class components sidestep this because they have instances. Function components don't. That's why React.forwardRef() exists โ it's an explicit opt-in that tells React: "yes, this component knows how to handle a ref."
The Fix: Wrap with React.forwardRef
Wrap your component with forwardRef. React then passes ref as the second argument to your function โ separate from props โ and you attach it to whichever DOM element you want to expose.
Before (broken)
function TextInput({ placeholder }) {
return <input placeholder={placeholder} />;
}
After (fixed)
import { forwardRef } from 'react';
const TextInput = forwardRef(function TextInput({ placeholder }, ref) {
return <input ref={ref} placeholder={placeholder} />;
});
export default TextInput;
The parent can now reach the underlying <input> DOM node directly:
import { useRef } from 'react';
import TextInput from './TextInput';
function Form() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current?.focus();
}
return (
<>
<TextInput ref={inputRef} placeholder="Type here" />
<button onClick={focusInput}>Focus</button>
</>
);
}
Using an Arrow Function (also valid)
const TextInput = forwardRef(({ placeholder }, ref) => (
<input ref={ref} placeholder={placeholder} />
));
TextInput.displayName = 'TextInput';
Always set displayName on arrow-function components. Without it, React DevTools labels the component ForwardRef โ fine until you're debugging a component tree with five of them stacked.
Forwarding to a Nested Element
The ref doesn't have to land on the root element. Put it wherever the caller actually needs access:
const Card = forwardRef(function Card({ children, className }, ref) {
return (
<div className="card-wrapper">
<div ref={ref} className={`card ${className}`}>
{children}
</div>
</div>
);
});
TypeScript: Typing forwardRef Correctly
The generic signature is forwardRef<RefType, PropsType>. Both type parameters matter:
import { forwardRef, InputHTMLAttributes } from 'react';
interface TextInputProps extends InputHTMLAttributes<HTMLInputElement> {
label?: string;
}
const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
function TextInput({ label, ...props }, ref) {
return (
<div>
{label && <label>{label}</label>}
<input ref={ref} {...props} />
</div>
);
}
);
export default TextInput;
Skip the generics and TypeScript infers ref as unknown. That causes type errors wherever the ref is used downstream โ often in a completely different file, which makes tracking down the root cause annoying.
Common Mistake: Forgetting to Attach the Ref
This one bites a lot of people. You wrap with forwardRef, the console warning disappears, and everything looks fine. But ref.current is still null:
// BUG: ref argument received but never used
const TextInput = forwardRef(function TextInput({ placeholder }, ref) {
return <input placeholder={placeholder} />; // ref not attached!
});
Receiving ref as an argument is only half the job. You have to actually pass it to a DOM element or another forwardRef component for anything to work.
Verify the Fix Worked
- The warning is gone from your browser console.
inputRef.currentis no longernullafter mount. Drop a quickuseEffectlog to confirm:
useEffect(() => {
console.log('ref:', inputRef.current); // Should print the DOM element, e.g. <input>
}, []);
- React DevTools shows your component by name (
TextInput, notForwardRef) โ that confirmsdisplayNameis set. - Imperative calls like
.focus()or.scrollIntoView()work without throwing.
Quick Reference
- Plain function component +
refprop โ warning, ref isnull - Wrap with
forwardRefโ second arg is the ref, attach it to a DOM node - TypeScript: use
forwardRef<HTMLElement, Props>generics โ both matter - Set
displayNameon arrow-function components for readable DevTools output - Warning gone but ref still
null? You wrapped correctly but forgot to attach the ref inside

