React · Hooks

useRef

useRef gives a component a box that survives re-renders — for reaching a DOM element, or for remembering a value without causing a re-render when it changes.

Intermediate 5 min Lesson 16 of 42Examples target React 19 with Vite.
Before you start:useEffect
0/42 in React
On this page · 5 min · Intermediate

Learning objectives#

  • Access DOM nodes with ref={myRef}
  • Focus, scroll and measure elements
  • Store mutable values such as timer ids
  • Know when to use a ref instead of state

Explanation#

const inputRef = useRef(null); then <input ref={inputRef} />. After rendering, inputRef.current is the DOM element: inputRef.current.focus().

A ref is a plain object { current: ... } that React keeps for the component’s lifetime. Changing ref.current does NOT re-render.

Use refs for values that matter to your code but not to what is displayed: timer ids, the previous value, whether a request is still current.

If the screen must change when a value changes, use state, not a ref.

Read and write refs in event handlers and effects, not during rendering. In React 19, ref is a normal prop, so your own components can accept ref directly.

Syntax#

JSX Illustrative
const inputRef = useRef(null);
<input ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus</button>


const timerId = useRef(null);
timerId.current = setInterval(tick, 1000);

Code example: A stopwatch and an auto-focusing search#

JSXStopwatch.jsx Runnable
import { useEffect, useRef, useState } from 'react';


export default function Stopwatch() {
  const [elapsed, setElapsed] = useState(0);          // shown on screen → state
  const timerId = useRef(null);                       // not shown → ref
  const searchRef = useRef(null);
  const renders = useRef(0);
  renders.current += 1;                               // counting renders does not cause one


  useEffect(() => {
    searchRef.current.focus();                        // DOM access after mount
    return () => clearInterval(timerId.current);      // clean up on unmount
  }, []);


  function start() {
    if (timerId.current) return;                      // already running
    timerId.current = setInterval(() => setElapsed(s => s + 1), 1000);
  }


  function stop() {
    clearInterval(timerId.current);
    timerId.current = null;
  }


  return (
    <>
      <input ref={searchRef} placeholder="I am focused on load" aria-label="Search" />
      <p>{elapsed} s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
      <button onClick={() => { stop(); setElapsed(0); }}>Reset</button>
      <small> (rendered {renders.current} times)</small>
    </>
  );
}
Expected output
The search box is focused as soon as the component appears. Start counts seconds; Stop pauses; Reset sets 0 s. Clicking Start twice does not create two timers, because the running timer’s id is kept in a ref.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. elapsed is state because it is displayed.
  2. timerId is a ref: the id is needed by stop() but changing it should not re-render.
  3. searchRef.current is the input element, focused in an effect after mount.
  4. The effect’s cleanup clears the interval if the component disappears.

Real-world use case#

A chat window keeps a ref to the message list and scrolls it to the bottom whenever a new message arrives; a video player keeps a ref to the <video> element to call play() and pause().

Common mistakes#

Using a ref for displayed data

Changing ref.current does not re-render, so the screen shows stale values. Use state for anything rendered.

Reading ref.current during render for DOM nodes

During the first render the element does not exist yet (current is null). Access it in effects or handlers.

Storing the timer id in a normal variable

let id = setInterval(...) is lost on the next render. Keep it in a ref.

Best practices#

  • State for what users see, refs for what code needs
  • Access DOM refs in effects or handlers
  • Clean up timers stored in refs
  • Prefer declarative props over imperative DOM calls when possible

Remember this#

ref = box that survives renders; changing it does not re-render.

ref={myRef} → myRef.current is the DOM element.

Timer ids, previous values, DOM nodes → refs.

Practice exercise#

Your task: Build a "Copy code" box: a readonly input with a coupon code and a button that selects the input’s text using a ref and shows "Copied!".

Need a hint?
inputRef.current.select(); then navigator.clipboard?.writeText(inputRef.current.value).
Show solution
JSX Runnable
import { useRef, useState } from 'react';


export default function CouponCopy() {
  const inputRef = useRef(null);
  const [copied, setCopied] = useState(false);


  function copy() {
    inputRef.current.select();
    navigator.clipboard?.writeText(inputRef.current.value);
    setCopied(true);
  }


  return (
    <>
      <input ref={inputRef} readOnly value="SAVE10" aria-label="Coupon code" />
      <button onClick={copy}>Copy</button>
      {copied && <span> Copied!</span>}
    </>
  );
}

Mini quiz#

useRef quizMultiple choice Question 1 of 3

What happens when you change ref.current?

Score so far: 0/0

Want more? Practise all React MCQs · React interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.