React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

typescriptreactrefsuseRefDOMmutable

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 JSX ref attribute, React manages setting .current to the DOM node.
  • In React 19, ref is a regular prop rather than a special attribute. Function components can accept ref directly in their props without forwardRef.
  • For mutable refs (storing timers, previous values, etc.), initialize with null and 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 hardcoding NodeJS.Timeout or number.
  • In React 19, forwardRef is no longer necessary. Components can accept ref as a regular prop: function Input({ ref }: { ref?: React.Ref<HTMLInputElement> }).

Gotchas

  • Accessing inputRef.current without a null check will error under strictNullChecks. Always use optional chaining (?.) or a guard.
  • Assigning to .current on 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 null when the component unmounts. Always handle the null case.

Alternatives

ApproachProsCons
useRef<T>(null)Safe, explicit null handlingRequires null checks everywhere
useRef<T>(null!)No null checks neededUnsafe if accessed before mount
Ref callback functionRuns on mount/unmount, useful for measurementsMore complex, fires on every render in React 18
React.createRef()Class component compatibleCreates a new ref each render in function components
ref as prop (React 19)No forwardRef wrapper neededOnly 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 ref attribute and React manages .current (setting it to the DOM node).
  • A mutable ref is one where you manage .current yourself (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 null because the DOM node does not exist until the component mounts.
  • useRef<HTMLInputElement>(null) gives the type HTMLInputElement | null, which matches this lifecycle.
How do I type a ref for a video element?
const videoRef = useRef<HTMLVideoElement>(null);
  • Use HTMLVideoElement as 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 ref attribute: 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, 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.
Gotcha: What happens if I access a DOM ref before the component mounts?
  • inputRef.current will be null before 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 .current during 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 useEffect updates ref.current after 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 null case since the callback fires with null on unmount.
What are the most common DOM ref types in TypeScript?
  • HTMLDivElement, HTMLInputElement, HTMLButtonElement, HTMLFormElement
  • HTMLCanvasElement, 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.