Table of Contents
- Introduction
- Understanding the useRef Hook
- Common useRef Use Cases
- Accessing DOM Elements with useRef
- What is forwardRef in React?
- When to Use forwardRef
- How to Use forwardRef in Practice
- Combining forwardRef and useImperativeHandle
- Practical Example: Creating a Custom Input with Focus
- Best Practices and Gotchas
- Conclusion
1. Introduction
React promotes a declarative approach to building UIs, which typically reduces the need for directly accessing the DOM. However, there are legitimate scenarios—like focusing an input, scrolling to a div, or measuring dimensions—where imperative access to DOM elements is necessary. That’s where useRef
and forwardRef
become essential.
This module covers everything you need to know about refs in React, including how to use them effectively, when to avoid them, and how forwardRef
enables passing refs through component hierarchies.
2. Understanding the useRef Hook
The useRef
hook is part of React’s Hooks API. It creates a mutable object that persists for the entire lifetime of the component and does not trigger re-renders when it changes.
Syntax:
jsxCopyEditconst myRef = useRef(initialValue);
myRef.current
holds the mutable value.- Commonly used to store DOM elements or mutable values that don’t need to cause a re-render.
3. Common useRef Use Cases
- Accessing DOM nodes directly (e.g.,
.focus()
on an input) - Storing mutable values like timers, previous state values, or third-party instances
- Avoiding re-renders when storing non-state data
- Integrating with non-React libraries that require a DOM element reference
4. Accessing DOM Elements with useRef
Here’s a basic example to programmatically focus an input field:
jsxCopyEditimport React, { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
const handleClick = () => {
inputRef.current?.focus();
};
return (
<>
<input ref={inputRef} type="text" placeholder="Click button to focus me" />
<button onClick={handleClick}>Focus Input</button>
</>
);
}
Here, inputRef.current
refers to the actual DOM node of the <input>
element.
5. What is forwardRef in React?
By default, refs do not pass through components. If you try to attach a ref
to a component, React will attach it to the component instance, not its inner DOM node.
To forward a ref from a parent to a child’s DOM node, use forwardRef
, a higher-order component provided by React.
6. When to Use forwardRef
Use forwardRef
when:
- Creating reusable component libraries
- Wrapping third-party components
- Composing custom inputs, buttons, modals, or form controls
- You want to expose internal DOM nodes or methods to parent components
7. How to Use forwardRef in Practice
Here’s how to create a button that forwards the ref
to its DOM node:
jsxCopyEditimport React, { forwardRef } from 'react';
const CustomButton = forwardRef((props, ref) => (
<button ref={ref} className="custom-button">
{props.children}
</button>
));
export default CustomButton;
Now you can use it like:
jsxCopyEditconst App = () => {
const buttonRef = useRef();
return (
<>
<CustomButton ref={buttonRef}>Click Me</CustomButton>
</>
);
};
Now buttonRef.current
refers to the underlying <button>
DOM element.
8. Combining forwardRef and useImperativeHandle
React also provides useImperativeHandle
to expose custom methods from a component to its parent using ref
.
Example:
jsxCopyEditconst FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focusInput: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} type="text" />;
});
And in the parent:
jsxCopyEditconst App = () => {
const fancyInputRef = useRef();
return (
<>
<FancyInput ref={fancyInputRef} />
<button onClick={() => fancyInputRef.current.focusInput()}>Focus</button>
</>
);
};
This makes your components more encapsulated and controlled while still allowing imperative actions.
9. Practical Example: Creating a Custom Input with Focus
Let’s combine forwardRef
, useRef
, and useImperativeHandle
:
jsxCopyEditconst CustomInput = forwardRef((props, ref) => {
const internalRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => internalRef.current.focus(),
clear: () => internalRef.current.value = ''
}));
return <input ref={internalRef} {...props} />;
});
const App = () => {
const inputRef = useRef();
return (
<>
<CustomInput ref={inputRef} placeholder="Custom input" />
<button onClick={() => inputRef.current.focus()}>Focus</button>
<button onClick={() => inputRef.current.clear()}>Clear</button>
</>
);
};
10. Best Practices and Gotchas
- Avoid overusing refs—prefer state and props for data flow.
- Use
useRef
for non-reactive data like timers or cached values. - Wrap components with
forwardRef
only when DOM access is required. - Combine with
useImperativeHandle
to expose minimal and safe APIs. - Ensure ref safety with optional chaining (
ref?.current?....
).
11. Conclusion
useRef
and forwardRef
are critical for scenarios where React’s declarative model needs to interact with imperative browser APIs or third-party libraries. When used judiciously, they offer powerful control over components without breaking the unidirectional data flow principle.
Summary:
useRef
is for persistent references that don’t cause re-renders.forwardRef
enables passing aref
through a component to its children.- Combine with
useImperativeHandle
to expose custom actions to the parent.