Refs & DOM Access
MedRefs 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