In React 19, useRef always requires an argument and always returns a single mutable RefObject, whether the ref holds a DOM node or a plain value. Choosing the right type argument still matters for autocomplete and for catching mistakes before they reach the browser, but the old split between a read-only DOM ref and a separate mutable value ref is gone.
Before React 19, calling useRef with an element type and null returned a read-only RefObject, and a separate MutableRefObject type covered writable refs. React 19 unified both into one mutable RefObject, and MutableRefObject is deprecated. Projects still on React 18 types will see the older read-only behavior.
A DOM ref
For a ref that points at a DOM node, pass the element type and null.
import { useRef } from "react";
function Search() {
const inputRef = useRef<HTMLInputElement>(null);
return <input ref={inputRef} />;
}The ref's current is typed HTMLInputElement or null, and TypeScript now allows writing to it. React still owns the DOM assignment in practice, so treat current as something you read, not something you write, even though the compiler no longer stops you. The element type parameter tells TypeScript which DOM methods, like focus, are available once current is not null.
A mutable value ref
When the ref holds a value rather than a node, pass the value's type and an initial value.
import { useRef } from "react";
function Counter() {
const countRef = useRef<number>(0);
return (
<button onClick={() => (countRef.current += 1)}>
Clicked {countRef.current} times
</button>
);
}current is a number you can read and write. The button updates the ref silently, but the label never changes because a ref does not trigger a re-render. Values used for display belong in state instead. Value refs are the right type for timer IDs and other values that never appear in the JSX.
A ref that starts empty, like a timer ID
Some refs must start empty but hold a value later, like a timer ID that does not exist until the timer starts.
import { useRef } from "react";
function Chat() {
const timeoutRef = useRef<number | null>(null);
function handleSend() {
timeoutRef.current = window.setTimeout(() => {}, 2000);
}
return <button onClick={handleSend}>Send</button>;
}Passing a union like number | null gives current the type number or null, and current is mutable in React 19. You can assign the timer ID once it exists and read it back to clear the timer. This pattern suits timer IDs, observers, and other values that start unset.
Calling useRef with no value
React 19 requires an argument, so useRef alone no longer compiles. Pass undefined explicitly when a ref has no meaningful starting value yet.
import { useRef, useEffect } from "react";
function Widget() {
const cleanupRef = useRef<(() => void) | undefined>(undefined);
useEffect(() => {
cleanupRef.current = () => console.log("cleaned up");
return () => cleanupRef.current?.();
}, []);
return <p>Widget</p>;
}This gives current the type of a cleanup function or undefined, and TypeScript accepts the call because an argument was supplied.
The overloads at a glance
Every call below returns a mutable RefObject in React 19. The type argument only changes what current can hold, not whether it is writable.
| Call | Type of current |
|---|---|
| Element type with null | HTMLInputElement or null |
| Concrete value | number |
| Element or value type with undefined | number or undefined |
What to learn next
Typing refs is one part of typing components. Continue with how useRef works for the runtime behavior, and how to type React props with TypeScript for the wider picture.
Rune AI
Key Insights
- React 19 requires useRef to receive an argument; call useRef(undefined) instead of useRef().
- useRef now returns one mutable RefObject type, so MutableRefObject is deprecated.
- Passing an element type with null still marks the ref for a DOM node.
- Passing a concrete value gives a mutable current of that type.
- Changing a ref's current never triggers a re-render, no matter how it is typed.
Frequently Asked Questions
Is a DOM ref's current still read-only in TypeScript?
Do I still need a union type like number or null for a nullable mutable ref?
What does useRef() with no argument return?
Conclusion
In React 19, useRef always requires an argument and always returns a single mutable RefObject, whether it holds a DOM node or a plain value. Pass an element type with null for a DOM ref, or a concrete value for a value ref.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.