Frontend Master

JavaScript Curriculum

useRef
+60 XP

useRef

medium
~25 min·60 XP

The search input needs to auto-focus when the menu loads. The video countdown needs a timer ID that persists across renders. The custom input component needs to forward its ref to the parent. All three use useRef.

useRef

useRef returns a mutable object { current: initialValue } that persists for the full lifetime of the component. Changing ref.current does NOT trigger a re-render. This makes it perfect for two different jobs: storing DOM nodes and storing mutable values.

Four useRef Use Cases

DOM access, mutable storage, previous value tracking, render counting:

useRef-explorer.jsx

useRef gives you a direct reference to a DOM node. Use it when you need to focus an input, measure an element, or trigger imperative DOM APIs.

function SearchInput() {
  const inputRef = useRef(null)

  const focusInput = () => {
    inputRef.current.focus()
    inputRef.current.select() // select all text
  }

  return (
    <>
      <input ref={inputRef} placeholder="Search..." />
      <button onClick={focusInput}>Focus</button>
    </>
  )
}
Live demo
inputRef.current → the DOM input node
💡ref.current vs state — the key difference
State: changing it schedules a re-render. Reading it gives you the snapshot from the last render. Ref: changing it does nothing to the render cycle. Reading it always gives you the current value. Use state for what the UI shows. Use ref for everything else.

useState vs useRef — Side by Side

See exactly when each causes re-renders:

ref-vs-state.jsx
useStateuseRef
Triggers re-renderYes — alwaysNo — never
Value persistsYes (per state update)Yes (across renders)
Read current valueVia state variableVia ref.current
Initial valueuseState(initial)useRef(initial)
Mutable directlyNo — must use setterYes — ref.current = x
Use forUI data — what to showTimers, DOM, counters
0
useState
0
useRef
// Interact with both counters
⚠️Never store UI-visible data in refs
If the value affects what the user sees, it belongs in useState. Refs are for side-channel storage — timers, DOM nodes, previous values, counters that the UI doesn't display. Reading ref.current in JSX works but the UI won't update when it changes.

forwardRef — Passing Refs to Children

How to let a parent hold a ref to a custom component's DOM node:

forwardRef.jsx
import { forwardRef } from 'react'

// ✓ forwardRef wraps the component
const FancyInput = forwardRef(function FancyInput(
  { placeholder, ...props },
  ref  // second parameter receives the forwarded ref
) {
  return (
    <input
      ref={ref}  // attach to DOM node
      placeholder={placeholder}
      {...props}
    />
  )
})

function Parent() {
  const inputRef = useRef(null)

  return (
    <>
      <FancyInput ref={inputRef} placeholder="Search" />
      <button onClick={() => inputRef.current.focus()}>
        Focus from parent
      </button>
    </>
  )
}
Live demo
forwardRef lets the parent hold a ref to a child component's DOM node. Essential for custom input components in design systems.
ℹ️When you need forwardRef
forwardRef is only needed when a custom component wraps a DOM element and the parent needs to access that DOM node directly. Common in design system input components, modal libraries, and drag-and-drop. For most components, you never need it.

Your Challenge

Write a SearchInput that: (1) auto-focuses on mount using useRef + useEffect. (2) Stores a debounce timer ID in a ref. (3) Wrap it in forwardRef so a parent component can call .focus() via a ref. Test by rendering <SearchInput ref={ref} /> and calling ref.current.focus() from a button.

Challenge

Build a SearchBar that auto-focuses on mount (useRef + useEffect). Add a stopwatch that stores the interval ID in a ref so start/stop work correctly. Wrap a custom TextInput in forwardRef so a parent can call .focus() on it.

useRefrefDOM-accessmutable-valueforwardReffocusprevious-valuerender-countimperativeHandle
useRef | Nexus Learn