Refs & DOM Access

Med

Refs provide an escape hatch from React's declarative model to access and manipulate DOM nodes directly. This includes focus management, scroll positioning, measuring elements, and integrating with non-React DOM libraries. forwardRef passes refs through component boundaries.

Interactive Visualization

Key Points

  • useRef creates a ref attached to a DOM element via the ref attribute
  • Ref.current is null until the component mounts
  • forwardRef allows parent components to ref a child's inner DOM element
  • useImperativeHandle customizes what the forwarded ref exposes
  • Callback refs run a function when the ref is attached or detached
  • In React 19, ref is available as a regular prop without forwardRef

Code Examples

forwardRef for Reusable Components

import { forwardRef, useRef, useImperativeHandle } from 'react'

interface InputProps {
  label: string
  placeholder?: string
}

const FancyInput = forwardRef<HTMLInputElement, InputProps>(
  ({ label, placeholder }, ref) => (
    <label>
      {label}
      <input ref={ref} placeholder={placeholder} />
    </label>
  )
)
FancyInput.displayName = 'FancyInput'

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

  return (
    <div>
      <FancyInput ref={inputRef} label="Name" />
      <button onClick={() => inputRef.current?.focus()}>
        Focus Input
      </button>
    </div>
  )
}

forwardRef passes the parent ref through to the inner input element, letting the parent focus it directly

useImperativeHandle for Custom API

import { forwardRef, useRef, useImperativeHandle } from 'react'

interface VideoPlayerHandle {
  play: () => void
  pause: () => void
  seekTo: (time: number) => void
}

const VideoPlayer = forwardRef<VideoPlayerHandle, { src: string }>(
  ({ src }, ref) => {
    const videoRef = useRef<HTMLVideoElement>(null)

    useImperativeHandle(ref, () => ({
      play: () => videoRef.current?.play(),
      pause: () => videoRef.current?.pause(),
      seekTo: (time: number) => {
        if (videoRef.current) videoRef.current.currentTime = time
      },
    }))

    return <video ref={videoRef} src={src} />
  }
)
VideoPlayer.displayName = 'VideoPlayer'

function MediaPage() {
  const playerRef = useRef<VideoPlayerHandle>(null)

  return (
    <div>
      <VideoPlayer ref={playerRef} src="/video.mp4" />
      <button onClick={() => playerRef.current?.play()}>Play</button>
      <button onClick={() => playerRef.current?.seekTo(0)}>Restart</button>
    </div>
  )
}

useImperativeHandle exposes a limited API instead of the raw DOM element, keeping the component encapsulated

Common Mistakes

  • Accessing ref.current during render before the DOM element is attached
  • Exposing the entire DOM node when only specific methods are needed
  • Forgetting to set displayName on forwardRef components for debugging

Interview Tips

  • Explain why refs are an escape hatch and when declarative solutions are better
  • Know useImperativeHandle for exposing a controlled API through refs
  • Discuss how React 19 simplifies ref forwarding by making ref a regular prop

Related Concepts