Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

useRef

Sometimes you need direct access to an element on the page: put the cursor in a field, scroll a list, measure a size. That is what the useRef hook is for. It creates a ref — an object with a single property, current, that is kept between renders. Changing current does not trigger a re-render; that is the main difference from state.

A ref to a DOM element

Let us put the cursor into the add-task field with a button. In src/components/AddTodoForm.jsx:

// src/components/AddTodoForm.jsx
import { useRef } from 'react';
 
export default function AddTodoForm() {
  const inputRef = useRef(null);
 
  function handleFocus() {
    inputRef.current.focus();
  }
 
  return (
    <>
      <input ref={inputRef} placeholder="New task" />
      <button onClick={handleFocus}>Enter a task</button>
    </>
  );
}

The ref attribute tells React: "put this DOM element into inputRef.current". After clicking the button, the cursor is in the field.

Focus after submitting

It is handy to return the cursor to the field after adding a task, so the next one can be typed right away:

// src/components/AddTodoForm.jsx
function handleSubmit(event) {
  event.preventDefault();
  onAdd(text);
  setText('');
  inputRef.current.focus();
}

Add a task — the field clears and the cursor stays in it.

A value without re-rendering

A ref also suits data that is not shown on the page, such as a timer id. A stopwatch — src/components/Stopwatch.jsx:

// src/components/Stopwatch.jsx
import { useRef, useState } from 'react';
 
export default function Stopwatch() {
  const [seconds, setSeconds] = useState(0);
  const timerRef = useRef(null);
 
  function start() {
    timerRef.current = setInterval(() => setSeconds((value) => value + 1), 1000);
  }
 
  function stop() {
    clearInterval(timerRef.current);
  }
 
  return (
    <>
      <p>Elapsed: {seconds} s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </>
  );
}

The timer id is needed only by the "Stop" button and is never shown, so it lives in a ref rather than in state.

A ref in your own component

In React 19 ref can be passed to your own component as an ordinary prop and attached to an element inside:

// src/components/TextInput.jsx
export default function TextInput({ ref, ...props }) {
  return <input ref={ref} className="text-input" {...props} />;
}

Now <TextInput ref={inputRef} /> works just like input. Older versions required the forwardRef function for this.

useRef or useState

useRefuseState
A change re-renders the componentNoYes
How to change itref.current = valueThrough the setter
What it is forDOM elements, timers, internal valuesEverything visible on screen

Common mistake

During rendering ref.current is still empty: React fills it only once the element is on the page. So read it in event handlers and effects, not in the component body:

// Wrong: on the first render inputRef.current is null.
const inputRef = useRef(null);
inputRef.current.focus();

To focus right after the element appears, call focus() inside useEffect or use the autoFocus attribute.

Thanks for reading!

Donate to the site — we work hard for you.