useRef & Mutable References

Easy

useRef returns a mutable ref object whose .current property persists across renders without triggering re-renders. It serves two purposes: accessing DOM elements directly and storing mutable values that need to survive re-renders but should not cause them.

Interactive Visualization

Key Points

  • Returns an object with a .current property that persists for the component lifetime
  • Mutating .current does NOT trigger a re-render
  • Use ref attribute on JSX elements to access the underlying DOM node
  • Common for focus management, measuring dimensions, and storing interval IDs
  • Can store any mutable value like a class instance field
  • Refs are the escape hatch from React's declarative model to imperative DOM access

Code Examples

DOM Access and Focus

import { useRef, useEffect } from 'react'

function SearchInput() {
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    inputRef.current?.focus()
  }, [])

  return <input ref={inputRef} type="text" placeholder="Search..." />
}

The ref attribute connects the JSX element to the ref object, giving direct access to the DOM node for imperative operations like focusing

Storing Mutable Values

import { useRef, useState, useCallback } from 'react'

function Stopwatch() {
  const [elapsed, setElapsed] = useState(0)
  const intervalRef = useRef<number | null>(null)
  const startTimeRef = useRef<number>(0)

  const start = useCallback(() => {
    startTimeRef.current = Date.now() - elapsed
    intervalRef.current = window.setInterval(() => {
      setElapsed(Date.now() - startTimeRef.current)
    }, 10)
  }, [elapsed])

  const stop = useCallback(() => {
    if (intervalRef.current !== null) {
      clearInterval(intervalRef.current)
      intervalRef.current = null
    }
  }, [])

  const reset = useCallback(() => {
    stop()
    setElapsed(0)
  }, [stop])

  return (
    <div>
      <p>{(elapsed / 1000).toFixed(2)}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

Interval IDs and timestamps are stored in refs because they need to persist across renders but changes should not trigger re-renders

Previous Value Tracking

import { useRef, useEffect } from 'react'

function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T | undefined>(undefined)

  useEffect(() => {
    ref.current = value
  })

  return ref.current
}

function PriceDisplay({ price }: { price: number }) {
  const prevPrice = usePrevious(price)
  const direction = prevPrice !== undefined
    ? price > prevPrice ? 'up' : price < prevPrice ? 'down' : 'same'
    : 'same'

  return <span className={direction}>{price}</span>
}

Refs can track previous render values because the effect updates .current after render, returning the old value during the current render

Common Mistakes

  • Reading or writing ref.current during render, which makes the component impure
  • Using refs to store values that should cause re-renders when changed
  • Forgetting that ref.current is null until the component mounts and the DOM element is created

Interview Tips

  • Explain the difference between useRef and useState for persisting values
  • Know when refs are appropriate vs when state is more correct
  • Discuss forwardRef for passing refs through component boundaries

Related Concepts