React Auto-Focus With Refs: Two Approaches Quiz
Two approaches to auto-focusing an input in React (`useRef` + `useEffect` vs the native `autoFocus` prop), plus companions on focusing after async state and on focusing inside a portal.
1,030 views
28
Auto-focus a text input on mount using useRef and useEffect. Implement the focus call so the input receives focus exactly once, immediately after the first render.
Examples
Example 1:
Input: mount the component
Output (Rendered DOM + Effect): the text input gains focus right after the first paint; subsequent re-renders do not steal focus.
Explanation: useEffect with [] runs once after mount; calling .focus() on the ref's current node moves focus there.import { useEffect, useRef } from 'react';
export default function FocusOnMount() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return (
<div>
<button type="button" onClick={() => inputRef.current?.focus()}>
Focus the text input
</button>
<input ref={inputRef} type="text" />
</div>
);
}
Auto-focus a text input on mount using the native autoFocus prop alone (no refs, no effects). Explain when this fallback is sufficient and when it is not.
Examples
Example 1:
Input: mount the component with <input autoFocus />
Output: the browser focuses the input automatically during initial page load (same semantics as the HTML autofocus attribute).
Explanation: React forwards autoFocus to the DOM, which applies the browser's built-in autofocus behavior on the very first render.export default function FocusViaAutoFocusProp() {
return (
<div>
<p>Type immediately - no JS, no ref.</p>
<input type="text" autoFocus />
</div>
);
}
Focus an input after asynchronous data arrives: fetch a user record, then focus the name input once the record is loaded. Use a ref + effect that depends on the loaded data.
Examples
Example 1:
Input: a user-edit form that mounts in a "loading..." state and then fetches /api/users/42
Output (Effect): the name input gains focus only after the fetch resolves and the form is rendered with the loaded values.
Explanation: gate the focus effect on the loaded user; while user is null, the input is not rendered, so .focus() would crash.import { useEffect, useRef, useState } from 'react';
export default function UserEditForm({ id }) {
const [user, setUser] = useState(null);
const nameRef = useRef(null);
useEffect(() => {
let alive = true;
fetch(`/api/users/${id}`)
.then((r) => r.json())
.then((u) => { if (alive) setUser(u); });
return () => { alive = false; };
}, [id]);
useEffect(() => {
if (user) nameRef.current?.focus();
}, [user]);
if (!user) return <p>loading...</p>;
return (
<form>
<input ref={nameRef} defaultValue={user.name} />
</form>
);
}
Focus the first input of a dialog rendered through a portal as soon as the dialog opens. Use a callback ref so the focus call runs exactly when the node is attached, and explain why a plain useRef + useEffect can race here.
Examples
Example 1:
Input: open a portal-based dialog; the dialog contains an <input> as its first focusable child.
Output (Effect): the input receives focus as soon as it is inserted into the DOM, before any user-visible flicker.
Explanation: a callback ref runs synchronously the moment the node is attached, so focus moves on the same commit; a plain ref + useEffect can lag a frame and is harder to coordinate with the dialog's open state.import { useCallback, useState } from 'react';
import { createPortal } from 'react-dom';
export default function DialogWithFocus({ isOpen, onClose, children }) {
const focusFirstInput = useCallback((node) => {
if (node) {
const input = node.querySelector('input, textarea, button, select, [tabindex]');
input?.focus();
}
}, []);
if (!isOpen) return null;
return createPortal(
<div onClick={onClose}
style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.4)',
display: 'grid', placeItems: 'center' }}>
<div ref={focusFirstInput} onClick={(e) => e.stopPropagation()}
style={{ background: 'white', padding: 24, borderRadius: 8 }}>
{children}
</div>
</div>,
document.body
);
}
