Refs cheatsheet.

DOM ref

const ref = useRef<HTMLInputElement>(null);

useEffect(() => {
  ref.current?.focus();
}, []);

<input ref={ref} />

Always init with null; access current with optional chaining.

Mutable value

const idRef = useRef(0);

function nextId() {
  return ++idRef.current;
}

Survives renders without triggering re-render. Good for IDs, timers, previous values.

Storing previous value

function usePrev<T>(value: T): T | undefined {
  const ref = useRef<T>();
  useEffect(() => { ref.current = value; }, [value]);
  return ref.current;
}

const prev = usePrev(count);

Timer / interval refs

const timerRef = useRef<NodeJS.Timeout | null>(null);

function start() {
  timerRef.current = setInterval(tick, 1000);
}

function stop() {
  if (timerRef.current) clearInterval(timerRef.current);
}

Callback ref

function MeasuredDiv() {
  const [size, setSize] = useState({ w: 0, h: 0 });
  
  const ref = useCallback((node: HTMLDivElement | null) => {
    if (!node) return;
    setSize({ w: node.offsetWidth, h: node.offsetHeight });
  }, []);
  
  return <div ref={ref}>w={size.w}</div>;
}

Called when node mounts / unmounts. Useful when ref-dependent state matters.

forwardRef (pre-React 19)

type Props = { label: string };

const Input = forwardRef<HTMLInputElement, Props>(({ label }, ref) => (
  <label>
    {label}
    <input ref={ref} />
  </label>
));

// Use:
const ref = useRef<HTMLInputElement>(null);
<Input ref={ref} label="Name" />

React 19: ref as prop

function Input({ label, ref }: { label: string; ref?: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} />;
}

No forwardRef needed in 19+.

useImperativeHandle

Expose a custom imperative API:

type Handle = { focus: () => void; clear: () => void };

const Input = forwardRef<Handle, { initial: string }>((props, ref) => {
  const inner = useRef<HTMLInputElement>(null);
  
  useImperativeHandle(ref, () => ({
    focus: () => inner.current?.focus(),
    clear: () => { if (inner.current) inner.current.value = ""; },
  }));
  
  return <input ref={inner} defaultValue={props.initial} />;
});

// Use:
const ref = useRef<Handle>(null);
ref.current?.focus();

Use sparingly — usually a sign you’re fighting React’s data flow.

Combining multiple refs

function mergeRefs<T>(...refs: React.Ref<T>[]): React.RefCallback<T> {
  return (value) => {
    refs.forEach((r) => {
      if (typeof r === "function") r(value);
      else if (r) (r as any).current = value;
    });
  };
}

<input ref={mergeRefs(ref1, ref2)} />

useRef vs useState

AspectuseStateuseRef
Trigger render?YesNo
Read in render?YesAvoid
Mutable?Replace onlyMutate freely

Rule: if the UI needs to update on change → state. Otherwise → ref.

Reading refs during render

function Comp() {
  const ref = useRef(0);
  ref.current;          // BAD: undefined behavior in concurrent rendering
  return <div />;
}

Read refs in effects, event handlers, or callbacks. Never in render.

Refs and SSR

useEffect(() => {
  // ref.current is set here (client only)
}, []);

ref.current is undefined on server; only set after mount. Wrap browser-only access.

DOM measurement pattern

const [width, setWidth] = useState(0);

const ref = useCallback((node: HTMLDivElement | null) => {
  if (node) setWidth(node.offsetWidth);
}, []);

useEffect(() => {
  if (!ref.current) return;
  const ro = new ResizeObserver(([entry]) => {
    setWidth(entry.contentRect.width);
  });
  ro.observe(ref.current);
  return () => ro.disconnect();
}, []);

Forwarding HTML attributes

const Input = forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
  (props, ref) => <input {...props} ref={ref} />
);

Common mistakes

  • Using ref when state would be cleaner.
  • Reading ref.current during render.
  • Forgetting null init for DOM refs.
  • Spreading ref onto a component that doesn’t forward — silent failure.
  • useImperativeHandle when an event/prop would suffice.

Read this next

If you want my refs + measurement utilities, they’re at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .