Himanshu SharmaSoftware engineer
Let’s talk

React · Lesson 21 of 38

Refs and uncontrolled inputs

Use useRef to focus and measure DOM elements, keep values that must not trigger a render, and read uncontrolled inputs, with React 19 ref as a prop.

18 minFormsUpdated 24 October 2026

By the end of this lessonChoose between state and refs, focus and read DOM elements with a ref, and build an uncontrolled input.

React wants you to describe the UI with state and props, and it handles the page for you. But sometimes you need to step outside that: focus a field, scroll to an element, measure a box, or keep a value that must not trigger a render. The tool for this is the ref.

useRef

useRef returns a plain object with a single property, current, that React keeps the same across renders:

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

function App() {
  const clicks = useRef(0);              // { current: 0 }
  const [shown, setShown] = useState(0);

  function handleClick() {
    clicks.current = clicks.current + 1;
    console.log("ref value:", clicks.current);
  }

  return (
    <div>
      <button onClick={handleClick}>Click (ref changes, no render)</button>
      <p>Last state value shown: {shown}</p>
      <p>Ref value read during render: {clicks.current}</p>
      <button onClick={() => setShown(clicks.current)}>Copy ref into state</button>
    </div>
  );
}

export default App;
result
Click (ref changes, no render)
Last state value shown: 0
Ref value read during render: 0
Copy ref into state
CONSOLE
ref value: 1
ref value: 2
ref value: 3

After three clicks the Console says the ref reached 3, but the "read during render" line still shows 0, because changing a ref does not cause a render. The text would update only when something else triggers a render; press the second button to copy the ref into state and everything refreshes. That is the difference in one picture:

State Ref
Survives renders yes yes
Changing it re-renders yes no
Read/write during rendering read avoid both (except setting up the initial value)
Use for anything shown on screen things outside the screen's output

Never read or write ref.current during rendering, apart from setting an initial value; a component must stay pure. Use it in event handlers and effects.

Focusing an element: DOM refs

The most common use is a reference to a DOM element. Create a ref, pass it in the ref prop of an element, and React sets current to the real element once it is on the page:

jsx
import { useRef } from "react";

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

  function handleFocus() {
    inputRef.current.focus();
    inputRef.current.value = "focused by code";
  }

  return (
    <div>
      <input ref={inputRef} placeholder="Click the button" />
      <button onClick={handleFocus}>Focus the input</button>
    </div>
  );
}

export default App;
result
Focus the input

inputRef.current is the actual <input> DOM node, so you can call any DOM method on it: focus(), scrollIntoView(), play() on a video, getBoundingClientRect() to measure. A ref starts as null and is filled in after the element is added to the page, so use it in handlers or effects, never in the component body on the first render.

Typical uses:

  • focus a field when a form opens or when a validation error appears,
  • scroll to a message or to the bottom of a chat,
  • play, pause or seek media,
  • measure the size or position of an element,
  • integrate with a non-React library that needs a DOM node.

Do not use refs to change what the page shows (text, classes, styles) that state can control. If you edit the DOM yourself, React will be unaware and may overwrite or conflict with your changes.

Refs for values that are not displayed

A ref is also a good place for a value that you need to remember between renders but that does not affect the output. A typical example is a timer id, so that you can cancel it later:

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

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

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

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

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

export default App;

The interval id is not part of what the user sees, and it must not change when the component re-renders (a normal let timerId would be recreated every render and lose the id). A ref keeps it. (A timer that should start automatically and stop when the component disappears is better handled in an effect, which the next lessons cover.)

Another use is remembering the previous value of something, or holding a flag such as "has the first render happened".

Refs and the DOM in lists

A ref can hold anything, including a map of elements. A ref callback lets you collect one per item:

jsx
import { useRef } from "react";

function App() {
  const itemRefs = useRef(new Map());
  const items = ["One", "Two", "Three"];

  function findLast() {
    const node = itemRefs.current.get("Three");
    console.log("found node for", node.textContent);
  }

  return (
    <div>
      <button onClick={findLast}>Find the last item</button>
      <ul>
        {items.map(item => (
          <li
            key={item}
            ref={node => {
              itemRefs.current.set(item, node);
              return () => itemRefs.current.delete(item);     // React 19: cleanup when removed
            }}
          >
            {item}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;
result
Find the last item
One
Two
Three
CONSOLE
found node for Three

In React 19 a callback ref may return a cleanup function, called when the element is removed; this is neat for registering and unregistering elements. (In earlier versions, the callback is called with null on removal instead.)

Uncontrolled inputs

In the Controlled Inputs lesson, state held the text of each field. An uncontrolled input is the opposite: the DOM keeps the text, and you read it when you need it. Give it a starting value with defaultValue (or defaultChecked), and read the current value through a ref:

jsx
import { useRef } from "react";

function App() {
  const nameRef = useRef(null);

  function handleSubmit(event) {
    event.preventDefault();
    console.log("Hello,", nameRef.current.value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameRef} defaultValue="" placeholder="Name" />
      <button>Greet</button>
    </form>
  );
}

export default App;
result
Greet
CONSOLE
Hello, React

This is less code for simple forms, and it re-renders nothing as you type. The cost is that React does not know the value while the user types, so you cannot easily validate on each keystroke, show a counter, or sync two fields. Choose:

  • Controlled when the value drives other things on screen, or you need live validation or formatting.
  • Uncontrolled (or FormData) for simple "collect on submit" forms and when integrating with plain-HTML-style code.

One input type is always uncontrolled in React: <input type="file" />. Its value can only be set by the user, so read ref.current.files or use FormData.

ref as a prop in React 19

When you want a parent to reach a DOM element that lives inside your own component (for instance, to focus the input inside a TextField), the parent passes a ref prop, and your component hands it to the element. In React 19, function components receive ref like any other prop:

jsx
import { useRef } from "react";

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

function App() {
  const fieldRef = useRef(null);

  return (
    <div>
      <TextField label="Search" ref={fieldRef} />
      <button onClick={() => { fieldRef.current.focus(); console.log("focused:", fieldRef.current.tagName); }}>
        Focus the search field
      </button>
    </div>
  );
}

export default App;
result
Search: Focus the search field
CONSOLE
focused: INPUT

Before React 19 you had to wrap the component in forwardRef to do this; that wrapper still works but is no longer needed. Use this sparingly: giving the parent control of a child's DOM node couples them. Often a prop like autoFocus, or a callback such as onSubmit, is a cleaner interface.

useRef rules of thumb

  1. If the screen should change when the value changes, it is state.
  2. If you need a handle to a DOM node, it is a ref.
  3. If you need a value that lives across renders but is invisible (timer ids, previous values, "is mounted" flags), it is a ref.
  4. Read and write refs in handlers and effects, not while rendering.
  5. Do not assign to the DOM to change what React renders. Refs are for actions (focus, scroll, measure), not for content.

Common mistakes

  • Reading ref.current in the component body on the first render, when it is still null.
  • Using a ref for something displayed, then wondering why the screen does not update.
  • Changing the DOM through a ref (ref.current.textContent = ...) instead of using state.
  • Writing to a ref during rendering, which breaks purity.
  • Forgetting defaultValue on an uncontrolled input that needs a starting value; using value makes it controlled again.
  • Using forwardRef in new React 19 code; just take ref as a prop.

Exercise

  1. Build a login form that focuses the email field when the page loads (hint: an effect, covered in the next lesson, or the autoFocus attribute) and focuses the password field when the email is submitted with Enter.
  2. Create a stopwatch with Start, Stop and Reset buttons using a ref for the interval id.
  3. Make a component that remembers how many times it has rendered, using a ref. Is it a good idea to display it?
  4. Write an uncontrolled form with a checkbox using defaultChecked, and print the form values on submit with FormData.

Key points

  • useRef returns an object with a current property that React keeps between renders; changing it does not cause a render.
  • Attach a ref to a DOM element with the ref prop to focus it, scroll to it, measure it or play a video; do this in handlers and effects, never during rendering.
  • Use a ref for values that are not shown on screen, such as timer ids; use state for anything the screen displays.
  • In React 19, function components receive ref as an ordinary prop, so forwardRef is no longer needed.

Check yourself

1. What happens when you change ref.current?
2. Which is the right use of a ref?
3. When is inputRef.current set to the DOM element?
4. What is an uncontrolled input?