Skip to content
Laviyx Learn

Try

React course menu

React useRef Hook: Accessing DOM Elements and Storing Values

Learn useRef: referencing DOM elements to focus inputs and scroll, storing values that persist without re-rendering such as timer ids, the difference between refs and state, passing refs to your own components, and when not to use refs.

Intermediate9 min readUpdated
On this page

useRef gives a component a box, { current: ... }, whose contents persist between renders but do not cause a re-render when changed. It has two main uses: getting hold of DOM elements, and remembering values that are not shown on screen.

Referencing DOM elements#

Pass a ref to an element's ref attribute, and React puts the DOM node in ref.current. You can then call browser methods such as focus(), scrollIntoView() or play():

JSX
import { useRef } from "react";

export default function App() {
  const inputRef = useRef(null);

  return (
    <div>
      <input ref={inputRef} placeholder="Search products" />
      <button onClick={() => inputRef.current.focus()} style={{ marginLeft: 8 }}>
        Focus search
      </button>
    </div>
  );
}
Result

ref.current is set after React creates the element, so read it in event handlers or effects, never during rendering.

Storing values without re-rendering#

A stopwatch needs to remember its interval id to stop it later. The id is never displayed, so it belongs in a ref, not in state:

JSX
import { useRef, useState } from "react";

export default function App() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);

  function start() {
    setStartTime(Date.now());
    setNow(Date.now());
    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => setNow(Date.now()), 10);
  }

  function stop() {
    clearInterval(intervalRef.current);
  }

  const seconds = startTime && now ? (now - startTime) / 1000 : 0;

  return (
    <div>
      <p style={{ fontSize: 24, fontFamily: "monospace" }}>{seconds.toFixed(2)} s</p>
      <button onClick={start}>Start</button>{" "}
      <button onClick={stop}>Stop</button>
    </div>
  );
}
Result

Ref or state?#

useStateuseRef
Changing it re-rendersyesno
Read during renderingyesno (it may be out of date)
Use foranything shown on screenDOM nodes, timer ids, previous values, values for event handlers

The demo below makes the difference visible: the ref's count increases in memory, but the screen only shows the new number when something else causes a re-render.

JSX
import { useRef, useState } from "react";

export default function App() {
  const refCount = useRef(0);
  const [stateCount, setStateCount] = useState(0);

  return (
    <div>
      <button onClick={() => (refCount.current += 1)}>ref +1</button>{" "}
      <button onClick={() => setStateCount(stateCount + 1)}>state +1</button>
      <p>ref: {refCount.current} · state: {stateCount}</p>
    </div>
  );
}
Result

Click "ref +1" three times: the screen still shows 0. Click "state +1": the re-render reveals the ref's value of 3.

Scrolling to an element#

JSX
import { useRef } from "react";

export default function App() {
  const bottomRef = useRef(null);
  const messages = Array.from({ length: 12 }, (_, i) => `Message ${i + 1}`);

  return (
    <div>
      <button onClick={() => bottomRef.current.scrollIntoView({ behavior: "smooth", block: "nearest" })}>
        Jump to latest
      </button>
      <div style={{ height: 100, overflowY: "auto", border: "1px solid #ddd", marginTop: 8, padding: 6 }}>
        {messages.map((m) => <p key={m} style={{ margin: "4px 0" }}>{m}</p>)}
        <div ref={bottomRef} />
      </div>
    </div>
  );
}
Result

Refs to your own components#

In React 19, ref can be passed to your own function components like any other prop, and forwarded to an element inside:

JSX
import { useRef } from "react";

function TextField({ label, ref }) {
  return (
    <label>
      {label} <input ref={ref} />
    </label>
  );
}

export default function App() {
  const nameRef = useRef(null);
  return (
    <div>
      <TextField label="Name" ref={nameRef} />
      <button onClick={() => nameRef.current.focus()} style={{ marginLeft: 8 }}>Focus</button>
    </div>
  );
}
Result

Older code (React 18 and before) wraps such components in forwardRef, which you will still see in many projects.

Don't overuse refs#

React manages the DOM for you. Use refs for things React has no prop for (focus, scrolling, measuring, media playback, third-party libraries), not to change text, classes or styles directly. Those should come from state and JSX; editing the DOM by hand gets overwritten on the next render.

Common mistakes#

Practice#

Practice exercise

Make a search form where pressing a "Clear" button empties the input (controlled with state) and then puts the cursor back in it.

Show hint

Keep the text in state and the input in a ref. In the button's handler, call setText("") and then inputRef.current.focus().

Show solution
JSX
import { useRef, useState } from "react";

export default function App() {
  const [text, setText] = useState("");
  const inputRef = useRef(null);

  function clear() {
    setText("");
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} value={text} onChange={(e) => setText(e.target.value)} placeholder="Search" />
      <button onClick={clear} style={{ marginLeft: 8 }}>Clear</button>
    </div>
  );
}
Result

Key takeaway#

Next: Managing Complex State with useReducer.

Finished this lesson?