useRef
useRef
is a React Hook that lets you reference a value that’s not needed for rendering.
const ref = useRef(initialValue)
Reference
useRef(initialValue)
Call useRef
at the top level of your component to declare a ref.
import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...
Parameters
initialValue
: The value you want the ref object’scurrent
property to be initially. It can be a value of any type. This argument is ignored after the initial render.
Returns
useRef
returns an object with a single property:
current
: Initially, it’s set to theinitialValue
you have passed. You can later set it to something else. If you pass the ref object to React as aref
attribute to a JSX node, React will set itscurrent
property.
On the next renders, useRef
will return the same object.
Caveats
- You can mutate the
ref.current
property. Unlike state, it is mutable. However, if it holds an object that is used for rendering (for example, a piece of your state), then you shouldn’t mutate that object. - When you change the
ref.current
property, React does not re-render your component. React is not aware of when you change it because a ref is a plain JavaScript object. - Do not write or read
ref.current
during rendering, except for initialization. This makes your component’s behavior unpredictable. - In Strict Mode, React will call your component function twice in order to help you find accidental impurities. This is development-only behavior and does not affect production. Each ref object will be created twice, but one of the versions will be discarded. If your component function is pure (as it should be), this should not affect the behavior.
Usage
Referencing a value with a ref
Call useRef
at the top level of your component to declare one or more refs.
import { useRef } from 'react';
function Stopwatch() {
const intervalRef = useRef(0);
// ...
useRef
returns a ref object with a single current
property initially set to the initial value you provided.
On the next renders, useRef
will return the same object. You can change its current
property to store information and read it later. This might remind you of state, but there is an important difference.
Changing a ref does not trigger a re-render. This means refs are perfect for storing information that doesn’t affect the visual output of your component. For example, if you need to store an interval ID and retrieve it later, you can put it in a ref. To update the value inside the ref, you need to manually change its current
property:
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
Later, you can read that interval ID from the ref so that you can call clear that interval:
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
By using a ref, you ensure that:
- You can store information between re-renders (unlike regular variables, which reset on every render).
- Changing it does not trigger a re-render (unlike state variables, which trigger a re-render).
- The information is local to each copy of your component (unlike the variables outside, which are shared).
Changing a ref does not trigger a re-render, so refs are not appropriate for storing information you want to display on the screen. Use state for that instead. Read more about choosing between useRef
and useState
.
Example 1 of 2: Click counter
This component uses a ref to keep track of how many times the button was clicked. Note that it’s okay to use a ref instead of state here because the click count is only read and written in an event handler.
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> ); }
If you show {ref.current}
in the JSX, the number won’t update on click. This is because setting ref.current
does not trigger a re-render. Information that’s used for rendering should be state instead.
Manipulating the DOM with a ref
It’s particularly common to use a ref to manipulate the DOM. React has built-in support for this.
First, declare a ref object with an initial value of null
:
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
// ...
Then pass your ref object as the ref
attribute to the JSX of the DOM node you want to manipulate:
// ...
return <input ref={inputRef} />;
After React creates the DOM node and puts it on the screen, React will set the current
property of your ref object to that DOM node. Now you can access the <input>
’s DOM node and call methods like focus()
:
function handleClick() {
inputRef.current.focus();
}
React will set the current
property back to null
when the node is removed from the screen.
Read more about manipulating the DOM with refs.
Example 1 of 4: Focusing a text input
In this example, clicking the button will focus the input:
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> </> ); }
Avoiding recreating the ref contents
React saves the initial ref value once and ignores it on the next renders.
function Video() {
const playerRef = useRef(new VideoPlayer());
// ...
Although the result of new VideoPlayer()
is only used for the initial render, you’re still calling this function on every render. This can be wasteful if it’s creating expensive objects.
To solve it, you may initialize the ref like this instead:
function Video() {
const playerRef = useRef(null);
if (playerRef.current === null) {
playerRef.current = new VideoPlayer();
}
// ...
Normally, writing or reading ref.current
during render is not allowed. However, it’s fine in this case because the result is always the same, and the condition only executes during initialization so it’s fully predictable.
Deep Dive
If you use a type checker and don’t want to always check for null
, you can try a pattern like this instead:
function Video() {
const playerRef = useRef(null);
function getPlayer() {
if (playerRef.current !== null) {
return playerRef.current;
}
const player = new VideoPlayer();
playerRef.current = player;
return player;
}
// ...
Here, the playerRef
itself is nullable. However, you should be able to convince your type checker that there is no case in which getPlayer()
returns null
. Then use getPlayer()
in your event handlers.
Detect DOM changes with a ref
In some scenarios, you might need to detect changes in the DOM, such as when a component’s children are dynamically updated. You can achieve this by using a ref
callback wrapped in useCallback
to create a MutationObserver. This approach allows you to observe changes in the DOM and perform actions based on those changes.
import { useState, useRef, useCallback } from "react"; import { useDrawReactLogo } from "./draw-logo"; export default function ReactLogo() { const [loading, setLoading] = useState(true); const logoRef = useRef(null); // the ref callback function should be wraped in // useCallback so the listener doesn't reconnect // on each render const setLogoRef = useCallback((node) => { logoRef.current = node; const observer = new MutationObserver(() => { if (node && node.children.length > 0) { setLoading(false); logoRef.current = null; observer.disconnect(); } }); observer.observe(node, { childList: true }); return () => { // When defining a ref callback cleanup function // it is important to re-assign the ref object // to null so that other references will not // point to the ghost element that no longer exists logoRef.current = null; observer.disconnect(); }; }, []); useDrawReactLogo(logoRef); return ( <div> {loading ? <div className="spinner">Loading...</div> : null} <div ref={setLogoRef}></div> </div> ); }
Deep Dive
When a ref callback function change, React will disconnect and reconnect on render. This is similar to a function dependency in an effect. React does this because new prop values may be needed to be passed to the ref callback function.
export default function ReactLogo() {
const setLogoRef = (node) => {
//...
};
//...
return <div ref={setLogoRef}></div>
}
To avoid unnecessary reconnections wrap your ref callback function in useCallback. Make sure to add any dependancies to the dependency array so the ref callback called with updated props when necessary.
export default function ReactLogo() {
const setLogoRef = useCallback((node) => {
//....
}, []);
//...
return <div ref={setLogoRef}></div>
}
import { useState, useCallback } from "react"; function WithoutCallback() { const [count, setCount] = useState(0); // 🚩 without useCallback, the callback changes every // render, which causes the listener to reconnect const handleRefEffect = (node) => { function onClick() { setCount((count) => count + 1); } console.log("without: adding event listener", node); node.addEventListener("click", onClick); return () => { console.log("without: removing event listener", node); node.removeEventListener("click", onClick); }; }; return <button ref={handleRefEffect}>Count {count}</button>; } function WithCallback() { const [count, setCount] = useState(0); // ✅ with useCallback, the callback is stable // so the listener doesn't reconnect each time const handleRefEffect = useCallback((node) => { function onClick() { setCount((count) => count + 1); } console.log("with: adding event listener", node); node.addEventListener("click", onClick); return () => { console.log("with: removing event listener", node); node.removeEventListener("click", onClick); }; }, []); return <button ref={handleRefEffect}>Count {count}</button>; } export default function App() { const [count, setCount] = useState(0); const handleRefEffect = (node) => { function onClick() { setCount((count) => count + 1); } console.log("adding event listener", node); node.addEventListener("click", onClick); return () => { console.log("removing event listener", node); node.removeEventListener("click", onClick); }; }; return ( <> <h1>without useCallback</h1> <WithoutCallback /> <h1>with useCallback</h1> <WithCallback /> </> ); }
Deep Dive
A ref
callback function with a cleanup function that does not set ref.current
to null
can result in a ref
to a unmounted node. Uncheck “Show Input” below and click “Submit” to see how the ref
to the unmounted <input>
is still accessible by the click handler for the form.
import { useRef, useState } from "react"; export default function MyForm() { const [showInput, setShowInput] = useState(true); const inputRef = useRef(); const handleCheckboxChange = (event) => { setShowInput(event.target.checked); }; const handleSubmit = (event) => { event.preventDefault(); if (inputRef.current) { alert(`Input value is: "${inputRef.current.value}"`); } else { alert("no input"); } }; const inputRefCallback = (node) => { inputRef.current = node; return () => { // ⚠️ You must set `ref.current` to `null` // in this cleanup function e.g. // `inputRef.current = null;` // to prevent hanging refs to unmounted DOM nodes }; }; return ( <form onSubmit={handleSubmit}> <div> <label> <input type="checkbox" checked={showInput} onChange={handleCheckboxChange} /> Show Input </label> </div> {showInput && ( <div> <label> Input: <input type="text" defaultValue="value from input DOM node" ref={inputRefCallback} /> </label> </div> )} <button type="submit">Submit</button> </form> ); }
To fix the hanging ref to the DOM node that is no longer rendered, set ref.current
to null
in the ref
callback cleanup function.
import { useRef } from "react";
function MyInput() {
const inputRef = useRef()
const inputRefCallback = (node) => {
inputRef.current = node;
return () => {
// ⚠️ You must set `ref.current` to `null` in this cleanup
// function to prevent hanging refs to unmounted DOM nodes
inputRef.current = null;
};
};
return <input ref={inputRefCallback}>
}
Troubleshooting
I can’t get a ref to a custom component
If you try to pass a ref
to your own component like this:
const inputRef = useRef(null);
return <MyInput ref={inputRef} />;
You might get an error in the console:
By default, your own components don’t expose refs to the DOM nodes inside them.
To fix this, find the component that you want to get a ref to:
export default function MyInput({ value, onChange }) {
return (
<input
value={value}
onChange={onChange}
/>
);
}
And then add ref
to the list of props your component accepts and pass ref
as a prop to the relevent child built-in component like this:
function MyInput({ value, onChange, ref }) {
return (
<input
value={value}
onChange={onChange}
ref={ref}
/>
);
};
export default MyInput;
Then the parent component can get a ref to it.
Read more about accessing another component’s DOM nodes.