Typing Refs
Recipe
Type useRef correctly for DOM element references and mutable value containers. Understand the difference between read-only DOM refs and mutable refs, and when to use each.
Working Example
// DOM element ref (read-only .current)
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
const focusInput = () => {
inputRef.current?.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}// Mutable ref (writable .current)
function Timer() {
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const [count, setCount] = useState(0);
const start = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
};
const stop = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
return (
<div>
<p>{count}</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}Deep Dive
How It Works
useRef<HTMLInputElement>(null)creates a ref object with{ current: HTMLInputElement | null }. When passed to a JSXrefattribute, React manages setting.currentto the DOM node.- In React 19, ref is a regular prop rather than a special attribute. Function components can accept
refdirectly in their props withoutforwardRef. - For mutable refs (storing timers, previous values, etc.), initialize with
nulland type accordingly:useRef<NodeJS.Timeout | null>(null). - The distinction between DOM refs and mutable refs is conceptual. Both use
useRef. The difference is whether React manages.current(DOM ref) or you manage it yourself (mutable ref).
Variations
Storing previous value:
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value;
});
return ref.current;
}Ref callback pattern:
function MeasuredBox() {
const [height, setHeight] = useState(0);
const measuredRef = (node: HTMLDivElement | null) => {
if (node) {
setHeight(node.getBoundingClientRect().height);
}
};
return (
<div ref={measuredRef}>
<p>This box is {height}px tall</p>
</div>
);
}Video element ref:
const videoRef = useRef<HTMLVideoElement>(null);
// Play/pause
const togglePlay = () => {
if (videoRef.current?.paused) {
videoRef.current.play();
} else {
videoRef.current?.pause();
}
};TypeScript Notes
- Common DOM ref types:
HTMLDivElement,HTMLInputElement,HTMLButtonElement,HTMLFormElement,HTMLCanvasElement,HTMLVideoElement,SVGSVGElement. ReturnType<typeof setInterval>is portable across Node and browser environments instead of hardcodingNodeJS.Timeoutornumber.- In React 19,
forwardRefis no longer necessary. Components can acceptrefas a regular prop:function Input({ ref }: { ref?: React.Ref<HTMLInputElement> }).
Gotchas
- Accessing
inputRef.currentwithout a null check will error understrictNullChecks. Always use optional chaining (?.) or a guard. - Assigning to
.currenton a DOM ref managed by React has no effect. React overwrites it during rendering. - Using
useRef<HTMLInputElement>(null!)(non-null assertion) removes the need for null checks but hides potential bugs if the ref is accessed before mount. - Ref callbacks fire with
nullwhen the component unmounts. Always handle thenullcase.
Alternatives
| Approach | Pros | Cons |
|---|---|---|
useRef<T>(null) | Safe, explicit null handling | Requires null checks everywhere |
useRef<T>(null!) | No null checks needed | Unsafe if accessed before mount |
| Ref callback function | Runs on mount/unmount, useful for measurements | More complex, fires on every render in React 18 |
React.createRef() | Class component compatible | Creates a new ref each render in function components |
ref as prop (React 19) | No forwardRef wrapper needed | Only available in React 19+ |
FAQs
What is the difference between a DOM ref and a mutable ref?
- A DOM ref is passed to a JSX
refattribute and React manages.current(setting it to the DOM node). - A mutable ref is one where you manage
.currentyourself (e.g., storing a timer ID or previous value). - Both use
useRef. The difference is who writes to.current.
Why do I need to pass null as the initial value for a DOM ref?
- DOM refs start as
nullbecause the DOM node does not exist until the component mounts. useRef<HTMLInputElement>(null)gives the typeHTMLInputElement | null, which matches this lifecycle.
How do I type a ref for a video element?
const videoRef = useRef<HTMLVideoElement>(null);- Use
HTMLVideoElementas the type parameter. - Access methods like
videoRef.current?.play()with optional chaining.
What does ReturnType<typeof setInterval> do and why use it?
- It extracts the return type of
setInterval, which differs between Node (NodeJS.Timeout) and browsers (number). - Using
ReturnType<typeof setInterval>is portable across both environments without hardcoding.
What is a ref callback and when should I use one?
- A ref callback is a function passed to the
refattribute:ref={(node) => { ... }}. - It fires when the element mounts (with the node) and unmounts (with
null). - Use it when you need to measure or observe an element on mount.
Do I still need forwardRef in React 19?
- No. In React 19,
refis a regular prop. Components can accept it directly:
function Input({ ref }: { ref?: React.Ref<HTMLInputElement> }) { ... }forwardRefstill works but is no longer necessary.
Gotcha: What happens if I access a DOM ref before the component mounts?
inputRef.currentwill benullbefore mount.- If you use
useRef<HTMLInputElement>(null!)to skip null checks, you hide this bug. - Always use optional chaining (
?.) or a null guard in production code.
Gotcha: Can I manually assign .current on a DOM ref managed by React?
- You can assign to it, but React overwrites
.currentduring rendering. - Your assignment will be lost on the next render. Do not rely on manual assignment for DOM refs.
How does the usePrevious custom hook work with refs?
- It stores the previous value in a mutable ref:
useRef<T | undefined>(undefined). - A
useEffectupdatesref.currentafter each render, so the returned value is always one render behind.
What TypeScript type should I use for a ref callback?
- The parameter type is the element type or
null:(node: HTMLDivElement | null) => void. - Always handle the
nullcase since the callback fires withnullon unmount.
What are the most common DOM ref types in TypeScript?
HTMLDivElement,HTMLInputElement,HTMLButtonElement,HTMLFormElementHTMLCanvasElement,HTMLVideoElement,SVGSVGElement
When does a ref callback fire with null?
- When the component unmounts, React calls the ref callback with
null. - In React 18, ref callbacks also fire on every re-render. In React 19, cleanup behavior is improved.