How to Properly Type React useRef with TypeScript
Problem
When using useRef with TypeScript in React, you’ve likely seen this error:
const inputRef = useRef<HTMLInputElement>();
inputRef.current.focus();
// 'inputRef.current' is possibly 'undefined'.
Passing null as the initial value gives a different error:
const inputRef = useRef<HTMLInputElement>(null);
inputRef.current.focus();
// 'inputRef.current' is possibly 'null'.
The fix isn’t just adding null checks everywhere. Understanding useRef overloads makes this clean.
Solution
useRef has two overloads:
// 1. DOM ref (readonly .current)
// Generic includes null, initial value is null
const inputRef = useRef<HTMLInputElement>(null);
// Type: RefObject<HTMLInputElement> — current is readonly
// 2. Mutable value storage (writable .current)
// Initial value type matches generic
const countRef = useRef<number>(0);
// Type: MutableRefObject<number> — current is writable
DOM refs are readonly because React manages them. Value refs are mutable because you manage them yourself.
Common patterns in practice:
// DOM element references
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);
// Timer ID storage
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
// Previous value tracking
const prevValueRef = useRef<string>("");
// Mount state tracking
const isMountedRef = useRef<boolean>(false);
In event handlers where the element is definitely mounted, non-null assertion is fine:
const handleSubmit = () => {
inputRef.current!.focus();
};
In useEffect, use conditional checks:
useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
Key Points
useRef<T>(null)→RefObject<T>(DOM refs, readonly current)useRef<T>(initialValue)→MutableRefObject<T>(value storage, writable current)- Always initialize DOM refs with
nulland add null checks when accessing - Use MutableRefObject for timers, previous values, and external library instances