React useRef Explained: Access the DOM and Keep Values Without Redrawing

React.js Full Course: Beginner to Advanced · Hooks and Data · 11 min read · Updated

A ref is a private notebook

  1. useRef makes a box with a current value. React keeps it between draws.
  2. Put ref on an element, and after drawing React fills current with the real element.
  3. Later, the person clicks a button.
  4. The handler uses ref.current to call a browser method on the real element.
  5. Changing a ref never redraws the screen. If the screen must change, use state instead.

State is for data that the screen shows. But sometimes you need to remember something that the screen does not show, or reach a real element on the page. For these cases, React gives you useRef. It is small, but very useful once you understand it.

What is a ref?

A ref is a box that holds a value. You make one with useRef(initialValue). It returns an object with a single field, current, that holds the value.

import { useRef } from 'react';

const box = useRef(0);
console.log(box.current);   // 0
box.current = 5;            // change it

Two facts make refs special:

  • React keeps the ref between draws, so the value is not lost, just like state.
  • Changing ref.current does not make React draw the component again, unlike state.

So a ref is like a private notebook: you can write in it and read from it, but writing does not tell the screen to change.

Use 1: focus an input

The most common use is to reach a real element in the page. Give the element a ref prop. After the element is drawn, React puts it in ref.current.

function SearchBox() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <div>
      <input ref={inputRef} placeholder="Search..." />
      <button onClick={handleClick}>Focus the search box</button>
    </div>
  );
}

Before the first draw, inputRef.current is null. After the page is drawn, it points to the real input element, so you can call browser methods such as .focus(). You can only use it in event handlers and effects, not while drawing.

A common case: focus the first field of a form when the page opens.

useEffect(() => {
  inputRef.current.focus();
}, []);

Another case: when a form shows an error, move the focus to the first wrong field. That helps people who use keyboards or screen readers.

Use 2: scroll to an element

function Page() {
  const bottomRef = useRef(null);

  return (
    <div>
      <button onClick={() => bottomRef.current.scrollIntoView({ behavior: 'smooth' })}>
        Go to the bottom
      </button>
      <div style={{ height: 1500 }}>Long content</div>
      <p ref={bottomRef}>You reached the bottom!</p>
    </div>
  );
}

A chat window uses the same trick: after a new message is added, scroll the last message into view.

Use 3: keep a value that does not affect the screen

Some values must survive between draws, but do not change what is on the screen. A timer id is the classic example:

function Stopwatch() {
  const [seconds, setSeconds] = useState(0);
  const timerId = useRef(null);

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

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

  useEffect(() => stop, []);   // stop when the component goes away

  return (
    <div>
      <p>{seconds} seconds</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

The seconds value is state, because the screen shows it. The timerId is a ref, because the screen does not show it, and we do not want a redraw when we set it.

Use 4: remember the previous value

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

function Price({ price }) {
  const before = usePrevious(price);
  return <p>Now {price}, before {before}</p>;
}

The hook returns the value from the last draw, because the effect updates the ref only after the draw is done. This small custom hook is a nice example of a ref storing a value quietly.

Ref or state: how to choose

  • If the screen must change when the value changes, use state.
  • If you only need to remember it, or hold a real element, use a ref.
  • Never read or write ref.current while the component is drawing. Do it in event handlers or effects.

Refs and other components

A ref on your own component does not work the same way as on a plain element. In React 19 you can pass ref as a normal prop to your own components:

function FancyInput({ ref, ...props }) {
  return <input ref={ref} className="fancy" {...props} />;
}

function Form() {
  const ref = useRef(null);
  return (
    <>
      <FancyInput ref={ref} />
      <button onClick={() => ref.current.focus()}>Focus</button>
    </>
  );
}

In older versions of React (18 and before) you had to wrap the component in a function called forwardRef. You may see that in older code. It does the same job.

Uncontrolled inputs with refs

You can read an input's value with a ref instead of keeping it in state. This is called an uncontrolled input. It is shorter for simple cases, because you do not redraw on every key press:

function QuickForm() {
  const emailRef = useRef(null);

  function handleSubmit(event) {
    event.preventDefault();
    console.log('Email is', emailRef.current.value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={emailRef} type="email" defaultValue="" />
      <button>Send</button>
    </form>
  );
}

Notice defaultValue instead of value: it sets only the starting text, and the browser keeps control after that.

Common mistakes

  • Reading ref.current when it is still null, for example during the first draw. Use it in handlers and effects.
  • Using a ref for data that the screen shows, so the screen does not update.
  • Forgetting .current. The ref is an object. The value is in ref.current.
  • Writing to ref.current while drawing. It makes the component unpredictable.
  • Using refs to change the page in ways React should handle. Prefer state for anything that changes what is drawn.

A worked example: a chat box

Here is a small chat that uses a ref twice: to scroll to the newest message, and to put the cursor back in the input after sending.

function Chat() {
  const [messages, setMessages] = useState([]);
  const [text, setText] = useState('');
  const endRef = useRef(null);
  const inputRef = useRef(null);

  useEffect(() => {
    endRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  function handleSubmit(event) {
    event.preventDefault();
    if (!text.trim()) return;
    setMessages([...messages, { id: crypto.randomUUID(), text: text.trim() }]);
    setText('');
    inputRef.current.focus();
  }

  return (
    <div>
      <ul>
        {messages.map((m) => <li key={m.id}>{m.text}</li>)}
      </ul>
      <div ref={endRef} />
      <form onSubmit={handleSubmit}>
        <input ref={inputRef} value={text} onChange={(e) => setText(e.target.value)} />
        <button>Send</button>
      </form>
    </div>
  );
}

The messages and the text are state, because the screen shows them. The two refs only point at real elements, so we can call browser methods on them. After each new message the effect scrolls an empty div at the bottom into view. After sending, the input gets focus again, so the person can keep typing without clicking. This is a good picture of the right split: state for what is shown, refs for reaching into the page.

Questions beginners often ask

Can I have many refs in one component? Yes. Make one useRef for each thing you need. For a list of items you can store the elements in an object inside one ref, or use a ref callback.

Is it wrong to change the page directly with a ref? For small things like focus and scrolling it is fine. Avoid using refs to change what is drawn, such as text or classes, because React will not know and may overwrite your changes. Use state for that.

Does changing a ref cause a render? No, and that is the point. If you need the screen to update, use state instead.

Your task

  1. Make a form that focuses the name field when the page opens.
  2. Make a button that scrolls smoothly to a section at the bottom of the page.
  3. Make a stopwatch with Start, Stop and Reset buttons using a ref for the timer id.
  4. Show the previous value of a counter using usePrevious.

How to check it worked

  • The cursor is in the name field when the page opens.
  • The stopwatch stops when you press Stop and does not run twice when you press Start twice.
  • No timers keep running after the component is removed.

Summary

  • useRef(initial) gives an object with current. React keeps it between draws, and changing it does not cause a redraw.
  • Put a ref on an element to get the real DOM node, for example to focus it or scroll it into view.
  • Use refs for timer ids, previous values and other things the screen does not show.
  • If the screen shows it, use state, not a ref.

Frequently asked questions

What is the difference between useRef and useState?

Changing state makes the component draw again. Changing a ref does not. Use state for data the screen shows, and a ref for values you only need to remember.

Why is ref.current null?

The element has not been drawn yet. Refs are filled after the first draw, so use them inside event handlers or effects.

What is forwardRef?

In React 18 and older, it was the way to pass a ref through your own component. In React 19, you can pass ref as a normal prop.

Should I use refs to read form values?

You can, for simple forms. This is called uncontrolled input. For forms that check values as people type, controlled inputs with state are easier.