React Managing Focus with useRef

In the last lesson, React Accessing DOM Elements, you learned to grab a real DOM node with useRef. Now let’s use that to control focus: moving the cursor into a field on purpose, selecting text, and clearing focus.

🤔 Why does focus matter?

Focus is just which element is “active” right now and will receive what you type. Here’s why moving it on purpose is worth the effort.

  • It saves the user a click. When a form or modal opens, the cursor should already be sitting in the first field, ready to go.
  • It helps people who use a keyboard. They don’t reach for a mouse, so they rely on focus landing in the right place.
  • It helps screen reader users, which is software that reads the page out loud. When focus moves, the screen reader announces the new element, so the person knows where they are.
  • It makes multi-step forms feel smooth. Finish one field, focus jumps to the next, and the user keeps typing.

Focus is an accessibility tool, not just a nicety

Good focus handling is one of the simplest ways to make an app usable for everyone. People who can’t or don’t use a mouse depend on it. So it’s not extra polish, it’s part of building the form right.

🎯 Focusing an input on mount

The classic case is a field that should be ready the moment it appears, like a search box or a login email field. We point a ref at the input, then focus it inside an effect that runs once.

This component creates a ref, attaches it to the input, and focuses that input right after the component first appears on screen.

import { useRef, useEffect } from "react";
function SearchBox() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="Search..." />;
}

Let’s read it line by line.

  • const inputRef = useRef(null) makes a ref, starting as null. After render it will point at the real input element.
  • ref={inputRef} on the input tells React: once this input is on the page, put it into inputRef.current.
  • useEffect(() => { ... }, []) runs after the first render. The empty array means it runs once, right when the component appears.
  • inputRef.current.focus() calls the browser’s built-in focus() method on that input, so the cursor lands inside it.

The key idea is the empty dependency array, the [] you pass as the second argument. It runs the focus once, on mount, and never again. So the cursor is waiting in the box the instant the user sees it.

The input must exist before you focus it

You focus inside an effect, not during render, for a reason. The input isn’t on the page yet while the component is rendering, so inputRef.current is still null. The effect runs after render, when the real input exists and inputRef.current points at it.

🖱️ Focusing an input on a button click

Sometimes you don’t want focus on mount. You want it to happen when the user does something, like clicking a button. It’s the same ref, but now you call focus() inside an event handler instead of an effect.

This component shows a button that, when clicked, moves the cursor into the text box.

import { useRef } from "react";
function FocusOnClick() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} placeholder="Type here..." />
<button onClick={handleClick}>Focus the input</button>
</div>
);
}

Here’s what’s happening.

  • inputRef points at the input, the same way as before.
  • handleClick is the event handler. Inside it we call inputRef.current.focus().
  • onClick={handleClick} on the button runs that handler when the user clicks.
  • So the click moves the cursor straight into the box, with no effect needed.

The difference from the last example is just timing. On mount you focus in an effect. On an action you focus in a handler. Either way it’s the same inputRef.current.focus() call, you just decide when to fire it.

⏭️ Moving focus to the next field

Once you can focus one input, you can move focus between several. This is what makes a form feel quick: finish one field, jump to the next on its own. You keep one ref per field, then call focus() on the one you want next.

This form focuses the second input as soon as the first one has enough characters typed, so the user doesn’t have to click ahead.

import { useRef } from "react";
function CodeForm() {
const firstRef = useRef(null);
const secondRef = useRef(null);
function handleFirstChange(e) {
if (e.target.value.length === 3) {
secondRef.current.focus();
}
}
return (
<form>
<input ref={firstRef} onChange={handleFirstChange} maxLength={3} />
<input ref={secondRef} maxLength={3} />
</form>
);
}

Let’s walk through the flow.

  • We make two refs, firstRef and secondRef, one for each input.
  • handleFirstChange runs on every keystroke in the first box.
  • When the typed value reaches three characters, secondRef.current.focus() moves the cursor into the second box.
  • The user finishes the first part and focus jumps ahead, so they keep typing without touching the mouse.

This is the same pattern as a one-time-code or PIN entry, where focus hops from box to box. The trick is just one ref per input, so you can reach any of them by name.

✨ Selecting text and removing focus

Focus has two close cousins on the same element: select() and blur(). They’re both built-in DOM methods, called the same way as focus().

This example focuses the input and also highlights whatever text is already inside it, so the user can retype right away.

import { useRef } from "react";
function EditField() {
const inputRef = useRef(null);
function handleEdit() {
inputRef.current.focus();
inputRef.current.select(); // highlight the existing text
}
return (
<div>
<input ref={inputRef} defaultValue="Old name" />
<button onClick={handleEdit}>Edit name</button>
</div>
);
}

Here’s what each method does.

  • inputRef.current.focus() puts the cursor in the box, like before.
  • inputRef.current.select() highlights all the text that’s already in the box. So the user can type a new value and it replaces the old one.
  • There’s a third one too: inputRef.current.blur() does the opposite of focus. It removes focus from the element, so the cursor leaves the box.

The select() method is great for “edit” buttons. The user clicks edit, the old value is highlighted, and one keypress replaces it. Small touch, but it feels really smooth.

⚠️ Common mistakes

The usual slip-up is calling focus() at the wrong time. Let’s compare the wrong and right ways.

This pair shows focusing during render (which crashes) versus focusing in an effect (which works).

// ❌ Wrong - focusing during render, the input doesn't exist yet
function SearchBox() {
const inputRef = useRef(null);
inputRef.current.focus(); // inputRef.current is null here, this crashes
return <input ref={inputRef} />;
}
// ✅ Right - focus in an effect, after the input is on the page
function SearchBox() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // input exists now, this works
}, []);
return <input ref={inputRef} />;
}

Here’s why the first one breaks.

  • During render the input isn’t on the page yet, so inputRef.current is still null. Calling .focus() on null throws an error and the component crashes.
  • The effect version waits until after render. By then the input exists and inputRef.current points at it, so .focus() works.
  • The rule is simple: never touch a ref’s element during render. Do it in an effect (for mount) or in an event handler (for an action).

Don't read inputRef.current during render

While the component is rendering, the DOM node doesn’t exist yet, so inputRef.current is null. Always reach for it after render, inside useEffect or inside an event handler. That’s the safe window.

🧩 What You’ve Learned

  • ✅ Managing focus saves clicks and is important for keyboard and screen reader users
  • ✅ Point a ref at an input with ref={inputRef}, then call inputRef.current.focus() to focus it
  • ✅ To focus on mount, call focus() inside useEffect(() => { ... }, []) so it runs once
  • ✅ To focus on an action, call focus() inside an event handler like onClick
  • ✅ Keep one ref per field to move focus to the next field as the user types
  • select() highlights the text in a box, and blur() removes focus from it
  • ✅ Never call focus() during render, since the element is still null then

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    How do you focus an input as soon as the component appears?

    Why: An effect with an empty dependency array runs once after the first render, when the input is on the page. That's the safe time to call focus().

  2. 2

    Why does calling inputRef.current.focus() during render crash?

    Why: During render the DOM node doesn't exist yet, so inputRef.current is null. Calling .focus() on null throws an error. Do it in an effect or a handler instead.

  3. 3

    What does inputRef.current.select() do?

    Why: select() highlights the existing text in the box, so the user can type a new value that replaces the old one. It's handy for edit buttons.

  4. 4

    How do you move focus to a second input when the first one is done?

    Why: Keep one ref per field. Then in a handler you call focus() on whichever input should come next, like secondRef.current.focus().

🚀 What’s Next?

So far you’ve used a ref to reach a DOM element and control its focus. But a ref can also just hold a value that survives re-renders without triggering one. Next we’ll look at that other side of useRef.

React Storing Mutable Values with useRef

Share & Connect