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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
