ProgramGuru

Working with DOM Refs

Working with DOM Refs

A teacher opens a student search panel and presses Focus search. The application needs to move keyboard focus to the actual browser input, which a string state value cannot do.

A DOM ref gives React a container or callback for an element it commits to the browser document. Once the node exists, an event handler can use its browser methods, such as focus, for a targeted imperative action.

  1. Understand

    Attach a ref to a native input element.

  2. Build

    Use a browser node only after it has been committed.

  3. Check

    Keep element content controlled by React rather than rewriting it manually.

Describe the node, then use its handle

  1. Render description

    JSX describes an input with a ref.

  2. Commit

    React creates or updates the real browser input.

  3. Event

    A button reads the node and calls focus.

Read this relationship alongside the complete code. This diagram explains the flow; it does not execute React.

Rendering and committing are different

Rendering is React calculating the element description by calling components. Committing is React applying the corresponding changes to browser elements. Before a newly described input has been committed, inputRef.current may still be null. A ref is therefore not a way to inspect a new browser element in the middle of rendering.

Focusing is an imperative browser operation: it tells an existing element to do something. That complements React’s declarative description of what the input should display. Use a ref for focused operations such as focus or scrolling, and keep the field’s value in the ordinary state and props data flow.

Focus a labeled student search

The native input receives ref={inputRef}. After the page commits, its DOM node becomes available through current. Focus search reads that node in a click handler. A status message is updated through state so the visible confirmation does not depend on a ref mutation causing a render.

React · JavaScript with JSX

import { useRef, useState } from 'react';
export default function App() {
  const inputRef = useRef(null);
  const [message, setMessage] = useState('Search ready');
  function focusSearch() {
    inputRef.current?.focus();
    setMessage('Search focused');
  }
  return <main><h1>Student search focus</h1>
    <label>Student search <input ref={inputRef} /></label>
    <button onClick={focusSearch}>Focus search</button>
    <p role="status">{message}</p>
  </main>;
}

Expected screen and behavior

Focus search moves keyboard focus into the labeled field and changes the message to Search focused.

Wait for an actual input node

React · Focus a labeled student search

import { useRef, useState } from 'react';
export default function App() {
  const inputRef = useRef(null);
  const [message, setMessage] = useState('Search ready');
  function focusSearch() {
    inputRef.current?.focus();
    setMessage('Search focused');
  }
  return <main><h1>Student search focus</h1>
    <label>Student search <input ref={inputRef} /></label>
    <button onClick={focusSearch}>Focus search</button>
    <p role="status">{message}</p>
  </main>;
}

The initial render returns an input description containing the ref container. During the commit, React attaches the browser input node to current. The teacher can then click the button; the handler reads that committed node and calls its native focus method.

Optional chaining avoids calling focus on null if the node is unavailable. When React removes the input, the object ref no longer points to the removed mounted node. Code handling a conditionally displayed field must account for that lifecycle. Immediately showing a field with setShow and then focusing it in the same handler may be too early because the new node has not been committed yet.

Keep focus useful to the teacher

Moving focus should support the teacher’s next action, not surprise them during unrelated edits. A deliberate Focus search button is clear. Opening a dialog or returning to an error field can also justify focus management, but the surrounding interaction should remain predictable for keyboard users.

Refs on custom components depend on what that component exposes. A native input has a browser node directly. A custom component may need a supported ref contract; React 19 allows function components to receive ref as a prop, while earlier patterns often use forwardRef. This lesson attaches refs only to native elements, so no version-specific forwarding setup is required.

Try the school task

React · starting example

import { useRef, useState } from 'react';
export default function App() {
  const inputRef = useRef(null);
  const [message, setMessage] = useState('Search ready');
  function focusSearch() {
    inputRef.current?.focus();
    setMessage('Search focused');
  }
  return <main><h1>Student search focus</h1>
    <label>Student search <input ref={inputRef} /></label>
    <button onClick={focusSearch}>Focus search</button>
    <p role="status">{message}</p>
  </main>;
}

Make Clear and focus empty a controlled Student search field and move focus back into it. Show the current query on the page. After entering Ravi and clicking the button, the query display should read (empty), and typing can continue in the same field.

Worked answer

The answer combines state for the value and a ref for focus. setQuery clears the rendered text, while focus targets the already mounted input. It does not remove or replace the input, so there is no need to wait for a newly created element. The two tools have different responsibilities.

React · JavaScript with JSX

import { useRef, useState } from 'react';
export default function App() {
  const inputRef = useRef(null);
  const [query, setQuery] = useState('');
  function clear() { setQuery(''); inputRef.current?.focus(); }
  return <main><h1>Clear student search</h1>
    <label>Student search <input ref={inputRef} value={query} onChange={event => setQuery(event.target.value)} /></label>
    <p>Query: {query || '(empty)'}</p>
    <button onClick={clear}>Clear and focus</button>
  </main>;
}

Expected screen and behavior

Entering Ravi shows Query: Ravi. Clear and focus empties the value and focuses the existing input.

Mistakes and boundaries

Do not manually remove children or overwrite text inside a subtree React manages. React’s next commit expects the structure it described. Using focus on a node is a targeted operation; rewriting the rendered attendance list with innerHTML creates conflicting ownership and can also expose unsafe markup.

A message saying Search focused is only one visible observation. Browser verification should also check the active element for a focus task. Extra StrictMode development checks can expose missing ref cleanup in callback patterns; do not rely on a fixed number of ref attachments as application behavior.

Check your reasoning

React · Focus a labeled student search

import { useRef, useState } from 'react';
export default function App() {
  const inputRef = useRef(null);
  const [message, setMessage] = useState('Search ready');
  function focusSearch() {
    inputRef.current?.focus();
    setMessage('Search focused');
  }
  return <main><h1>Student search focus</h1>
    <label>Student search <input ref={inputRef} /></label>
    <button onClick={focusSearch}>Focus search</button>
    <p role="status">{message}</p>
  </main>;
}

Choose the answer that follows the data and the interaction, then read the explanation for each alternative.

When is the new native input node available for ordinary focus use?
Choose one answer. You can try again.

Answer and explanation

After React commits that input to the browser document.

Correct. Rendering describes an element; the commit makes its browser node available.

Carry the idea forward

Refs can target committed browser nodes for focus and other limited operations. Keep visible values in React’s data flow. Next, useEffect explains synchronization that follows committed renders rather than a particular button action.

Read the primary reference: React: manipulating the DOM with refs.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: useEffect: Synchronize with an External System →