useRef
The useRef hook lets you reference a value that’s not needed for rendering. Unlike state, changing a ref does not trigger a re-render. It is commonly used for accessing DOM elements directly or storing mutable values that persist across renders.
Basic Usage: DOM Access
The most common use case for useRef is accessing and manipulating DOM nodes, such as focusing an input, scrolling to an element, or measuring dimensions.
import { useRef } from 'react';
export default function FocusInput() {
const inputRef = useRef(null);
const handleFocus = () => {
// Access the actual DOM node via the .current property
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Click button to focus me" />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
}
Storing Mutable Values
Because updating a ref doesn't trigger a re-render, it is perfect for storing values like interval IDs, previous state values, or flags that don't directly affect the UI.
import { useState, useRef, useEffect } from 'react';
export default function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const timerId = useRef(null); // Stores the interval ID
const start = () => {
if (timerId.current !== null) return;
timerId.current = setInterval(() => setSeconds(s => s + 1), 1000);
};
const stop = () => {
clearInterval(timerId.current);
timerId.current = null;
};
useEffect(() => {
// Cleanup on unmount
return () => clearInterval(timerId.current);
}, []);
return (
<div>
<p>{seconds} seconds</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
forwardRef and useImperativeHandle
By default, React components don't expose their internal DOM nodes to parents. To allow a parent component to attach a ref to a child component, the child must be wrapped in forwardRef.
import { forwardRef, useRef } from 'react';
// Child component wrapped in forwardRef
const CustomInput = forwardRef((props, ref) => {
return <input {...props} ref={ref} className="custom-input" />;
});
export default function Form() {
const inputRef = useRef(null);
return <CustomInput ref={inputRef} />;
}
If you want to expose only specific functions (like a custom focus or reset method) instead of the entire DOM node, you combine forwardRef with the useImperativeHandle hook.
Key Takeaways
- Updating
ref.currentdoes not trigger a re-render. - Never read or write
ref.currentduring the render phase (inside the component body before return), as it makes your component unpredictable. Only do so in event handlers oruseEffect.