useRef hook creates a ref that persists without triggering re-renders
The useRef hook creates a reference object that React retains between re-renders. Unlike setState, changing a ref does not cause a component to re-render. You access the current value through the ref.current property.
Refs can store values that don't impact rendering
Refs are useful for storing timeout IDs, DOM elements, and other objects that don't affect the component's rendering output. A ref is like a secret pocket of your component that React doesn't track.
Manipulating the DOM with refs to focus an input
You can use a ref to access DOM elements managed by React for operations like focusing a node, scrolling to it, or measuring its size and position. React provides no built-in way to do these things, so you need a ref to the DOM node.
Refs store values that don't trigger re-renders
Refs are like state variables that don't trigger re-renders when you set them. Refs remain between renders. You can use refs for storing other things outside React, like timer IDs.
useRef Hook basic usage
Import useRef from 'react'. Declare a ref inside a component using const myRef = useRef(null). Pass the ref as the ref attribute to a JSX tag, like <div ref={myRef}>. The useRef Hook returns an object with a single property called current. Initially, myRef.current will be null. When React creates a DOM node, React puts a reference to that node into myRef.current. You can then access the DOM node from event handlers and use browser APIs defined on it.
Common use cases for refs: focus, scroll, measure
Refs are an escape hatch for when you need to access DOM elements managed by React. Common use cases include focusing a node, scrolling to it, or measuring its size and position. React automatically updates the DOM to match render output, so components won't often need to manipulate it directly.
Example: Focusing a text input with ref
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
Focus the input
</button>
</>
);
}
This example shows how to use a ref to focus an input element when a button is clicked.
Multiple refs in a component
You can have more than a single ref in a component. For example, firstCatRef, secondCatRef, and thirdCatRef can all be declared with useRef(null) and passed to different elements.
scrollIntoView browser API with refs
The scrollIntoView() method can be called on a DOM node accessed through a ref. It accepts an options object with properties: behavior ('smooth' or 'auto'), block ('nearest', 'start', 'center', 'end'), and inline ('nearest', 'start', 'center', 'end'). Example: myRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
Cannot call useRef in loops or conditions
Hooks must only be called at the top-level of your component. You cannot call useRef in a loop, in a condition, or inside a map() call. Attempting this will cause an error.
Ref callback for managing lists of refs
Pass a function to the ref attribute instead of a ref object. This is called a ref callback. React will call your ref callback with the DOM node when it's time to set the ref, and call the cleanup function returned from the callback when it's time to clear it. This lets you maintain your own array or Map and access any ref by index or ID. Example: ref={(node) => { const map = getMap(); map.set(cat, node); return () => { map.delete(cat); }; }}
Ref callback runs twice in Strict Mode during development
When Strict Mode is enabled, ref callbacks will run twice in development. This helps find bugs in callback refs.
Passing refs to child components
You can pass refs from parent component to child components just like any other prop. However, refs are not a standard prop by default. To accept a ref in a child component, you must explicitly declare it: function MyInput({ ref }) { return <input ref={ref} />; }
Pitfall: manually manipulating another component's DOM nodes
Refs are an escape hatch. Manually manipulating another component's DOM nodes can make your code fragile. It is best practice to avoid this when possible.
useImperativeHandle to restrict exposed API
Use useImperativeHandle to restrict the exposed functionality when passing a ref to a child component. Instead of exposing the entire DOM node, you can provide a custom object that only exposes certain methods. Example: useImperativeHandle(ref, () => ({ focus() { realInputRef.current.focus(); }, }));
When React attaches refs during render and commit phases
React splits every update into two phases: render and commit. During render, React calls components to figure out what should be on screen. During commit, React applies changes to the DOM. React sets ref.current during the commit phase. Before updating the DOM, React sets the affected ref.current values to null. After updating the DOM, React immediately sets them to the corresponding DOM nodes. During the first render, DOM nodes have not yet been created, so ref.current will be null. During rendering of updates, DOM nodes haven't been updated yet, so it's too early to read them.
Access refs from event handlers
Usually, you will access refs from event handlers. If you want to do something with a ref but there is no particular event to do it in, you might need an Effect.
flushSync to synchronously update DOM before accessing refs
State updates are queued in React. If you call setTodos and then immediately try to access a ref to the newly added item, the ref will not yet reflect the update because the DOM hasn't been updated. To fix this, import flushSync from 'react-dom' and wrap the state update: flushSync(() => { setTodos([...todos, newTodo]); }); This instructs React to update the DOM synchronously right after the code wrapped in flushSync executes.
Best practices: only use refs for non-destructive actions
Refs are an escape hatch. You should only use them when you have to 'step outside React'. Stick to non-destructive actions like focusing and scrolling. If you try to modify the DOM manually, you can risk conflicting with the changes React is making.
Pitfall: modifying DOM nodes managed by React causes crashes
Modifying, adding children to, or removing children from elements that are managed by React can lead to inconsistent visual results or crashes. For example, using the DOM remove() API to forcefully remove an element from the DOM, then trying to use setState to show it again will lead to a crash because React doesn't know how to continue managing it correctly.
Safe DOM modifications with refs
You can safely modify parts of the DOM that React has no reason to update. For example, if some <div> is always empty in the JSX, React won't have a reason to touch its children list. Therefore, it is safe to manually add or remove elements there.
Example: Scrolling image carousel with ref
import { useRef, useState } from 'react';
import { flushSync } from 'react-dom';
export default function CatFriends() {
const selectedRef = useRef(null);
const [index, setIndex] = useState(0);
return (
<>
<nav>
<button onClick={() => {
flushSync(() => {
if (index < catList.length - 1) {
setIndex(index + 1);
} else {
setIndex(0);
}
});
selectedRef.current.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center'
});
}}>
Next
</button>
</nav>
<div>
<ul>
{catList.map((cat, i) => (
<li key={cat.id} ref={index === i ? selectedRef : null}>
<img src={cat.imageUrl} />
</li>
))}
</ul>
</div>
</>
);
}
This example shows using flushSync to ensure DOM is updated before scrolling.
Example: Managing a list of refs with Map using ref callback
import { useRef, useState } from 'react';
export default function CatFriends() {
const itemsRef = useRef(null);
const [catList, setCatList] = useState(setupCatList);
function scrollToCat(cat) {
const map = getMap();
const node = map.get(cat);
node.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
});
}
function getMap() {
if (!itemsRef.current) {
itemsRef.current = new Map();
}
return itemsRef.current;
}
return (
<>
<nav>
<button onClick={() => scrollToCat(catList[0])}>Neo</button>
</nav>
<ul>
{catList.map((cat) => (
<li key={cat.id} ref={(node) => {
const map = getMap();
map.set(cat, node);
return () => {
map.delete(cat);
};
}}>
<img src={cat.imageUrl} />
</li>
))}
</ul>
</>
);
}
This example shows using a ref callback with a Map to manage multiple refs in a list.
Example: Playing and pausing video with ref
import { useState, useRef } from 'react';
export default function VideoPlayer() {
const [isPlaying, setIsPlaying] = useState(false);
const ref = useRef(null);
function handleClick() {
const nextIsPlaying = !isPlaying;
setIsPlaying(nextIsPlaying);
if (nextIsPlaying) {
ref.current.play();
} else {
ref.current.pause();
}
}
return (
<>
<button onClick={handleClick}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<video width="250" ref={ref} onPlay={() => setIsPlaying(true)} onPause={() => setIsPlaying(false)}>
<source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" type="video/mp4" />
</video>
</>
);
}
This example shows calling play() and pause() on a video element via ref.
Example: useImperativeHandle to expose custom API
import { useRef, useImperativeHandle } from 'react';
function MyInput({ ref }) {
const realInputRef = useRef(null);
useImperativeHandle(ref, () => ({
focus() {
realInputRef.current.focus();
},
}));
return <input ref={realInputRef} />;
}
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<MyInput ref={inputRef} />
<button onClick={handleClick}>Focus the input</button>
</>
);
}
This example shows using useImperativeHandle to restrict the exposed API to only the focus method.
useRef Hook returns object with current property
The useRef Hook returns an object with a single property called `current`. This property holds the initial value passed to useRef. For example, `useRef(0)` returns `{ current: 0 }`.
Refs do not trigger re-renders when changed
Unlike state, changing a ref's value does not trigger a component re-render. The ref itself is retained by React between re-renders, but modifying ref.current does not queue a re-render.
Ref current value is mutable and intentionally untracked
The ref.current property is intentionally mutable, meaning you can both read and write to it. React does not track changes to ref.current. This makes refs an escape hatch from React's one-way data flow.
Import useRef from React
To use refs in a component, import the useRef Hook from React: `import { useRef } from 'react';`
When to use refs vs state for component memory
When a piece of information is used for rendering, keep it in state. When a piece of information is only needed by event handlers and changing it doesn't require a re-render, using a ref may be more efficient.
Do not read or write ref.current during rendering
You should not read or write the ref.current value during rendering. If some information is needed during rendering, use state instead. Reading ref.current while rendering makes your component's behavior difficult to predict. The only exception is code like `if (!ref.current) ref.current = new Thing()` which only sets the ref once during the first render.
Common use cases for refs
Typically, refs are used when a component needs to step outside React and communicate with external APIs. Common use cases include: storing timeout IDs, storing and manipulating DOM elements, and storing other objects that aren't necessary to calculate the JSX.
Ref is a regular JavaScript object
A ref is a plain JavaScript object with a current property. Because it is a regular object, mutating ref.current changes the value immediately and synchronously. Unlike state, there is no batching or asynchronous behavior.
Treat refs as an escape hatch
Refs are useful when working with external systems or browser APIs. If much of an application's logic and data flow relies on refs, you might want to rethink your approach. Use them sparingly.
Refs vs state comparison table
Refs: `useRef(initialValue)` returns `{ current: initialValue }` | Doesn't trigger re-render when changed | Mutable—you can modify ref.current outside rendering | You shouldn't read/write ref.current during rendering. State: `useState(initialValue)` returns current value and setter function `[value, setValue]` | Triggers re-render when changed | "Immutable"—must use setter function to modify | You can read state at any time; each render has its own snapshot of state.
Refs persist between re-renders like state
Like state, refs let you retain information between re-renders of a component. However, unlike state, changing a ref does not cause a re-render.
Local variables don't survive re-renders
Regular local variables like `let timeoutID = null` don't survive between re-renders because every render runs the component and initializes its variables from scratch. Use a ref instead to preserve values across re-renders.
Passing refs to DOM elements with ref attribute
When you pass a ref to a `ref` attribute in JSX, like `<div ref={myRef}>`, React will put the corresponding DOM element into `myRef.current`. Once the element is removed from the DOM, React will update `myRef.current` to be `null`.
Don't use refs when value is used for rendering
If a component's ref.current value is used to calculate the rendering output, this is a sign the information should be in state instead of a ref. Use state for values that affect the UI.
Each component instance has its own refs
Each instance of a component gets its own separate ref. When multiple components use useRef, they each have their own independent refs that don't interfere with each other.
useRef implementation principle
Although provided by React as a built-in, useRef could theoretically be implemented on top of useState. It essentially creates a state variable without a setter and always returns the same object: `function useRef(initialValue) { const [ref, unused] = useState({ current: initialValue }); return ref; }`
Counter with useRef example - does not update display
Example showing why refs should not be used for values displayed in the UI:
```js
import { useRef } from 'react';
export default function Counter() {
let countRef = useRef(0);
function handleClick() {
countRef.current = countRef.current + 1;
}
return (
<button onClick={handleClick}>
You clicked {countRef.current} times
</button>
);
}
```
This example demonstrates that clicking the button does not update the displayed count because ref changes don't trigger re-renders.
Counter with useRef basic example
Example showing how to use useRef to track a value without re-rendering:
```js
import { useRef } from 'react';
export default function Counter() {
let ref = useRef(0);
function handleClick() {
ref.current = ref.current + 1;
alert('You clicked ' + ref.current + ' times!');
}
return (
<button onClick={handleClick}>
Click me!
</button>
);
}
```
This shows ref.current can be read and modified, but the component doesn't re-render with each increment.
Stopwatch with refs and state example
Example showing how to combine refs and state in a stopwatch component:
```js
import { useState, useRef } from 'react';
export default function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current);
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>
Start
</button>
<button onClick={handleStop}>
Stop
</button>
</>
);
}
```
This demonstrates using state for rendering (startTime, now) and a ref to store the interval ID which is not used for rendering.
Chat input with timeout ref example
Example showing how to use a ref to store a timeout ID for later cancellation:
```js
import { useState, useRef } from 'react';
export default function Chat() {
const [text, setText] = useState('');
const [isSending, setIsSending] = useState(false);
const timeoutRef = useRef(null);
function handleSend() {
setIsSending(true);
timeoutRef.current = setTimeout(() => {
alert('Sent!');
setIsSending(false);
}, 3000);
}
function handleUndo() {
setIsSending(false);
clearTimeout(timeoutRef.current);
}
return (
<>
<input
disabled={isSending}
value={text}
onChange={e => setText(e.target.value)}
/>
<button disabled={isSending} onClick={handleSend}>
{isSending ? 'Sending...' : 'Send'}
</button>
{isSending && <button onClick={handleUndo}>Undo</button>}
</>
);
}
```
This demonstrates storing a timeout ID in a ref so it can be accessed across multiple renders and cleared in a different event handler.
Debounced button with individual refs example
Example showing how each component instance needs its own ref to avoid interference:
```js
import { useRef } from 'react';
function DebouncedButton({ onClick, children }) {
const timeoutRef = useRef(null);
return (
<button onClick={() => {
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
onClick();
}, 1000);
}}>
{children}
</button>
);
}
export default function Dashboard() {
return (
<>
<DebouncedButton
onClick={() => alert('Spaceship launched!')}
>
Launch the spaceship
</DebouncedButton>
<DebouncedButton
onClick={() => alert('Soup boiled!')}
>
Boil the soup
</DebouncedButton>
<DebouncedButton
onClick={() => alert('Lullaby sung!')}
>
Sing a lullaby
</DebouncedButton>
</>
)
}
```
This demonstrates that each button component has its own ref, so they don't interfere with each other's timeouts.
Read latest state with ref example
Example showing how to use a ref alongside state to read the latest value in asynchronous code:
```js
import { useState, useRef } from 'react';
export default function Chat() {
const [text, setText] = useState('');
const textRef = useRef(text);
function handleChange(e) {
setText(e.target.value);
textRef.current = e.target.value;
}
function handleSend() {
setTimeout(() => {
alert('Sending: ' + textRef.current);
}, 3000);
}
return (
<>
<input
value={text}
onChange={handleChange}
/>
<button onClick={handleSend}>
Send
</button>
</>
);
}
```
This demonstrates using both state (for rendering) and a ref (to access the latest value in async code) together.