useRef Hook
The useRef Hook creates a mutable object with a single property called current. Unlike state, updating a ref does not cause a component to re-render. This makes useRef ideal for accessing DOM elements, storing mutable values, or keeping references across renders.
Basic syntax:
import { useRef } from "react";
const myRef = useRef(initialValue);
Accessing DOM Elements
A common use case is directly interacting with DOM elements, such as focusing an input field.
import { useRef } from "react";
function InputFocus() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Focus</button>
</>
);
}
Here, inputRef.current points to the actual DOM node.
Storing Mutable Values
useRef can also store values that persist across renders without causing re-renders.
const renderCount = useRef(0); renderCount.current += 1;
This is useful for tracking previous values, timers, or counters that should not affect UI updates.
Forward Refs
By default, refs cannot be passed to custom components. Forward refs solve this problem by allowing a parent component to forward a ref to a child component’s DOM element.
Basic example without forwardRef (this will not work):
<Input ref={inputRef} />
To enable ref forwarding, React provides forwardRef.
import { forwardRef } from "react";
const Input = forwardRef((props, ref) => {
return <input ref={ref} />;
});
Usage:
function Parent() {
const inputRef = useRef();
return <Input ref={inputRef} />;
}
Now, the parent can directly access the child’s input element.
useImperativeHandle
useImperativeHandle is used with forwardRef to control what values or methods are exposed to the parent. Instead of exposing the entire DOM node, you can expose specific functions or properties.
Example:
import { forwardRef, useImperativeHandle, useRef } from "react";
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = "";
}
}));
return <input ref={inputRef} />;
});
Usage in parent:
function Parent() {
const inputRef = useRef();
return (
<>
<CustomInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>
<button onClick={() => inputRef.current.clear()}>Clear</button>
</>
);
}
Here, the parent can only access the methods explicitly exposed by the child, improving encapsulation and safety.
Real-World Scenario
In real-world applications, these patterns are commonly used for form inputs, modals, custom UI libraries, and animation controls. For example, a reusable input component may expose only focus and reset methods to the parent while hiding internal implementation details. Similarly, a modal component can expose open and close methods using useImperativeHandle.
Important Notes and Best Practices
Avoid overusing refs; React encourages declarative patterns over imperative ones. Use refs only when necessary, such as for DOM access or performance optimizations. Prefer state and props for data flow. Use useImperativeHandle sparingly to maintain clean and predictable component APIs.
In summary, useRef allows persistent mutable values and DOM access, forward refs enable ref passing to child components, and useImperativeHandle provides controlled exposure of imperative methods. Together, they support advanced interaction patterns while keeping React components maintainable and efficient.