Mastering useRef in React with TypeScript: Avoid Common Pitfalls

This article explains how to correctly use React's useRef hook with TypeScript across several real‑world scenarios, compares different initialization patterns, highlights typical mistakes with useLayoutEffect, and provides best‑practice solutions for forwarding refs, merging refs, and exposing imperative handles.

Alibaba Cloud Developer
Alibaba Cloud Developer
Alibaba Cloud Developer
Mastering useRef in React with TypeScript: Avoid Common Pitfalls

Scenario 1: Accessing DOM Elements

Which of the following patterns is correct for obtaining a DOM element with useRef?

function MyComponent() {
  // Write 1
  const ref = useRef();

  // Write 2
  const ref = useRef(undefined);

  // Write 3
  const ref = useRef(null);

  // 🚨 This code intentionally contains a bug, see below.
  useLayoutEffect(() => {
    const rect = ref.current.getBoundingClientRect();
  }, [ref.current]);

  return <div ref={ref} />;
}

When TypeScript is enabled, the three patterns differ in the inferred type of ref.current: useRef<HTMLDivElement>() yields MutableRefObject<HTMLDivElement | undefined>, requiring a runtime check for undefined. useRef<HTMLDivElement>(undefined) causes a type error because the initial value does not match HTMLDivElement. useRef<HTMLDivElement>(null) yields RefObject<HTMLDivElement> where ref.current is HTMLDivElement | null, which is the recommended way to access DOM nodes.

Make sure strictNullChecks is enabled in tsconfig so the null case is correctly enforced.

Scenario 2: useLayoutEffect with DOM Elements

The buggy code from Scenario 1 also demonstrates two common mistakes:

Missing null‑check inside useLayoutEffect when accessing ref.current.

Incorrect dependency array – using ref.current directly instead of a condition that reflects the element's presence. useLayoutEffect runs after the virtual DOM is rendered but before the browser paints, making it ideal for measuring elements without causing visual flicker. When the element is conditionally rendered, the effect should depend on the same condition (e.g., visible) so it re‑runs whenever the element appears.

function MyComponent({ visible }: { visible: boolean }) {
  const ref = useRef<HTMLDivElement>(null);
  useLayoutEffect(() => {
    if (ref.current) {
      const rect = ref.current.getBoundingClientRect();
    }
  }, [visible]); // ✅ depend on the visibility flag
  return <>{visible && <div ref={ref} />}</>;
}

If the goal is simply to perform side‑effects after rendering (e.g., logging video time), a normal useEffect with a state‑based ref is sufficient.

Scenario 3: Forwarding and Merging Refs

When a component both receives a forwarded ref and needs its own internal ref, handling the two possible ref shapes (function or object) can become verbose. The react-merge-refs utility simplifies this:

import { mergeRefs } from "react-merge-refs";

const MyComponent = forwardRef((props, ref) => {
  const localRef = React.useRef<HTMLDivElement>(null);
  useLayoutEffect(() => {
    const rect = localRef.current.getBoundingClientRect();
  }, []);
  return <div ref={mergeRefs([localRef, ref])} />;
});

Scenario 4: Exposing Imperative Methods

Complex components like forms often expose an imperative API (e.g., reset()) via a ref. The correct way to type‑safe expose such methods is to use useImperativeHandle:

const MyComponent = forwardRef((props, ref) => {
  const actions = useMemo(() => ({
    reset() { /* ... */ }
  }), []);
  useImperativeHandle(ref, () => actions, [actions]);
  return <div />;
});

Scenario 5: Declaring Ref Types in Component Exports

When exporting a component that uses forwardRef, the resulting type is

ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>

. To avoid polluting the component's props with a ref field, either:

Include the ref type directly in the component's generic parameters, or

Use ComponentProps<typeof MyComponent> to extract the final prop shape.

When a component needs to forward a ref through multiple layers, rename the prop (e.g., actionRef) to prevent accidental overriding of the built‑in ref handling.

Bonus: Related TypeScript Types for Refs

PropsWithoutRef<Props>

: Removes the ref property from props, useful for HOCs. PropsWithRef<Props>: Guarantees that ref is not a string. ForwardedRef<T>: The type of a ref passed to a component via forwardRef. MutableRefObject<T> and RefObject<T>: Results of useRef and createRef respectively. ForwardRefExoticComponent: The return type of forwardRef, combining props without ref and the appropriate ref attributes.

Using the most appropriate TypeScript interfaces ensures compatibility across React versions and prevents subtle type‑checking errors.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendTypeScriptReacthooksRefuseRef
Alibaba Cloud Developer
Written by

Alibaba Cloud Developer

Alibaba's official tech channel, featuring all of its technology innovations.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.