useFocusLoading saved progress…

useFocus

Build a custom React hook that lets a component move keyboard focus to one of its elements on demand. Reading and writing the DOM directly is something React usually hides from you, but focus is a case where you genuinely need an imperative escape hatch: an element has to be told "you're focused now," and there's no prop for that. useFocus hands back a ref to attach to a focusable element, plus a focus() function that focuses it whenever you call it.

Signature

function useFocus(): [
  ref: React.RefObject<HTMLElement>, // attach to a focusable element
  focus: () => void,                 // call to focus that element
];

The hook returns a two-item tuple. ref goes on the element via <input ref={ref} />; focus is a stable function you can call from an event handler.

Examples

function SearchBox() {
  const [inputRef, focusInput] = useFocus();

  return (
    <div>
      <input ref={inputRef} placeholder="Search…" />
      <button onClick={focusInput}>Jump to search</button>
    </div>
  );
}
// Clicking "Jump to search" moves the cursor into the input.
// Before the element mounts, ref.current is null. Calling focus() then
// must be a safe no-op, not a crash:
const [ref, focus] = useFocus();
focus(); // ref.current is null here — does nothing, throws nothing

Notes

  • focus must be a no-op when nothing is attached. If ref.current is null — the element hasn't mounted yet, or the ref was never placed — calling focus() must do nothing rather than throw.
  • focus must keep a stable identity. Returning a brand-new function each render would break any child that depends on a steady reference (a memoized component, an effect dependency array). The same function reference should survive across renders.
  • The ref is also stable. It's the same object every render, so React keeps pointing it at the same DOM node.
  • The element must be focusable. Inputs, buttons, links, and anything with tabIndex can receive focus. Focusing a plain <div> with no tabIndex does nothing — that's the DOM's rule, not the hook's concern.
Loading editor…