//
Search across all documentation pages
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.
// 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
useRef<HTMLInputElement>(null) creates a ref object with { current: HTMLInputElement | null }. When passed to a JSX ref attribute, React manages setting .current to the DOM node.ref directly in their props without forwardRef.null and type accordingly: useRef<NodeJS.Timeout | null>(null).useRef. The difference is whether React manages .current (DOM ref) or you manage it yourself (mutable ref).Storing previous value:
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current =
Ref callback pattern:
function MeasuredBox() {
const [height, setHeight] = useState(0);
const measuredRef = (node: HTMLDivElement | null) => {
if (node) {
setHeight(node.
Video element ref:
const videoRef = useRef<HTMLVideoElement>(null);
// Play/pause
const togglePlay = () => {
if (videoRef.current?.paused) {
videoRef.current.play();
} else {
videoRef.current?.pause();
}
HTMLDivElement, HTMLInputElement, HTMLButtonElement, HTMLFormElement, HTMLCanvasElement, HTMLVideoElement, SVGSVGElement.ReturnType<typeof setInterval> is portable across Node and browser environments instead of hardcoding NodeJS.Timeout or number.forwardRef is no longer necessary. Components can accept ref as a regular prop: function Input({ ref }: { ref?: React.Ref<HTMLInputElement> }).inputRef.current without a null check will error under strictNullChecks. Always use optional chaining (?.) or a guard..current on a DOM ref managed by React has no effect. React overwrites it during rendering.useRef<HTMLInputElement>(null!) (non-null assertion) removes the need for null checks but hides potential bugs if the ref is accessed before mount.null when the component unmounts. Always handle the null case.| 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+ |
ref attribute and React manages .current (setting it to the DOM node)..current yourself (e.g., storing a timer ID or previous value).useRef. The difference is who writes to .current.null as the initial value for a DOM ref?null because the DOM node does not exist until the component mounts.useRef<HTMLInputElement>(null) gives the type HTMLInputElement | null, which matches this lifecycle.const videoRef = useRef<HTMLVideoElement>(null);HTMLVideoElement as the type parameter.videoRef.current?.play() with optional chaining.ReturnType<typeof setInterval> do and why use it?setInterval, which differs between Node (NodeJS.Timeout) and browsers (number).ReturnType<typeof setInterval> is portable across both environments without hardcoding.ref attribute: ref={(node) => { ... }}.null).ref is a regular prop. Components can accept it directly:function Input({ ref }: { ref?: React.Ref<HTMLInputElement> }) { ... }forwardRef still works but is no longer necessary.inputRef.current will be null before mount.useRef<HTMLInputElement>(null!) to skip null checks, you hide this bug.?.) or a null guard in production code..current on a DOM ref managed by React?.current during rendering.usePrevious custom hook work with refs?useRef<T | undefined>(undefined).useEffect updates ref.current after each render, so the returned value is always one render behind.null: (node: HTMLDivElement | null) => void.null case since the callback fires with null on unmount.HTMLDivElement, HTMLInputElement, HTMLButtonElement, HTMLFormElementHTMLCanvasElement, HTMLVideoElement, SVGSVGElementnull?null.