How to Optimize Forwarding Refs in React
Optimizing forwarding refs in React is crucial for maintaining
high-performing applications, especially when building reusable
component libraries. This article explores practical strategies to
optimize React’s forwardRef API, including combining it
with React.memo to prevent redundant re-renders, using the
useImperativeHandle hook to limit exposed DOM properties,
and avoiding performance pitfalls like inline ref callbacks.
Combine forwardRef with React.memo
By default, wrapping a component in React.forwardRef
does not prevent it from re-rendering when its parent re-renders. To
optimize performance and prevent unnecessary renders, you should combine
forwardRef with React.memo.
import React, { forwardRef, memo } from 'react';
const OptimizedInput = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
export default memo(OptimizedInput);Note: Order matters. Always pass the forwardRef
component inside memo to ensure React correctly tracks the
component’s reference while maintaining memoization.
Limit Exposed DOM Nodes with useImperativeHandle
Exposing a raw DOM node directly to a parent component can lead to
unintended side effects, such as direct style mutations or unauthorized
state changes. To optimize component encapsulation and reduce rendering
bugs, use useImperativeHandle to limit the API exposed to
the parent.
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} {...props} />;
});By using this hook, the parent component can only call
.focus() on the ref, rather than having full access to the
underlying <input> element’s DOM properties.
Avoid Inline Ref Callbacks
When you pass an inline function as a ref callback inside a forwarded
component, React will execute the callback twice during every update:
first with null, and then with the DOM element.
To optimize this and avoid unnecessary function instantiations, use a
stable ref object created with useRef, or wrap your custom
callback function in a useCallback hook.
// Avoid this:
<input ref={(node) => { console.log(node); }} />
// Optimize with useCallback:
const handleRef = useCallback((node) => {
if (node !== null) {
console.log(node);
}
}, []);
return <input ref={handleRef} />;Handle Conditional Ref Assignment Wisely
If you only need to forward a ref under specific conditions, avoid
dynamically switching the ref prop value between null and
the ref object during renders. Doing so forces React to detach and
reattach the ref on every render cycle. Instead, keep the ref reference
stable and handle conditional logic inside the ref handler or the
component’s lifecycle hooks.