Skip
Arish's avatar

11. useRef Hook


useRef Hook

The useRef hook creates a mutable reference that persists across renders without causing re-renders when updated.

Basic Syntax

jsx
1import { useRef } from 'react'
2
3function Component() {
4  const ref = useRef(initialValue)
5  
6  // Access value with .current
7  console.log(ref.current)
8  
9  // Update without re-render
10  ref.current = newValue
11}

DOM References

The most common use - accessing DOM elements:

jsx
1function TextInput() {
2  const inputRef = useRef(null)
3  
4  const focusInput = () => {
5    inputRef.current.focus()
6  }
7  
8  const selectAll = () => {
9    inputRef.current.select()
10  }
11  
12  return (
13    <div>
14      <input ref={inputRef} type="text" />
15      <button onClick={focusInput}>Focus</button>
16      <button onClick={selectAll}>Select All</button>
17    </div>
18  )
19}

Auto-Focus on Mount

jsx
1function SearchBox() {
2  const inputRef = useRef(null)
3  
4  useEffect(() => {
5    inputRef.current?.focus()
6  }, [])
7  
8  return <input ref={inputRef} placeholder="Search..." />
9}

Scroll to Element

jsx
1function LongPage() {
2  const topRef = useRef(null)
3  const bottomRef = useRef(null)
4  
5  const scrollToTop = () => {
6    topRef.current?.scrollIntoView({ behavior: 'smooth' })
7  }
8  
9  const scrollToBottom = () => {
10    bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
11  }
12  
13  return (
14    <div>
15      <div ref={topRef}>Top of page</div>
16      
17      {/* Long content */}
18      
19      <div ref={bottomRef}>Bottom of page</div>
20      
21      <button onClick={scrollToTop}>Go to top</button>
22      <button onClick={scrollToBottom}>Go to bottom</button>
23    </div>
24  )
25}

Video/Audio Control

jsx
1function VideoPlayer() {
2  const videoRef = useRef(null)
3  const [isPlaying, setIsPlaying] = useState(false)
4  
5  const togglePlay = () => {
6    if (isPlaying) {
7      videoRef.current.pause()
8    } else {
9      videoRef.current.play()
10    }
11    setIsPlaying(!isPlaying)
12  }
13  
14  const skip = (seconds) => {
15    videoRef.current.currentTime += seconds
16  }
17  
18  return (
19    <div>
20      <video ref={videoRef} src="/video.mp4" />
21      <button onClick={togglePlay}>
22        {isPlaying ? 'Pause' : 'Play'}
23      </button>
24      <button onClick={() => skip(-10)}>-10s</button>
25      <button onClick={() => skip(10)}>+10s</button>
26    </div>
27  )
28}

Storing Previous Values

jsx
1function Counter() {
2  const [count, setCount] = useState(0)
3  const prevCountRef = useRef(0)
4  
5  useEffect(() => {
6    prevCountRef.current = count
7  }, [count])
8  
9  return (
10    <div>
11      <p>Current: {count}</p>
12      <p>Previous: {prevCountRef.current}</p>
13      <button onClick={() => setCount(c => c + 1)}>+</button>
14    </div>
15  )
16}
17
18// Custom hook for previous value
19function usePrevious(value) {
20  const ref = useRef()
21  
22  useEffect(() => {
23    ref.current = value
24  }, [value])
25  
26  return ref.current
27}
28
29// Usage
30function Component() {
31  const [count, setCount] = useState(0)
32  const prevCount = usePrevious(count)
33}

Storing Instance Variables

Unlike state, updating ref doesn't trigger re-render:

jsx
1function Stopwatch() {
2  const [time, setTime] = useState(0)
3  const [isRunning, setIsRunning] = useState(false)
4  const intervalRef = useRef(null)
5  
6  const start = () => {
7    if (!isRunning) {
8      setIsRunning(true)
9      intervalRef.current = setInterval(() => {
10        setTime(t => t + 1)
11      }, 1000)
12    }
13  }
14  
15  const stop = () => {
16    if (isRunning) {
17      setIsRunning(false)
18      clearInterval(intervalRef.current)
19    }
20  }
21  
22  const reset = () => {
23    stop()
24    setTime(0)
25  }
26  
27  useEffect(() => {
28    return () => clearInterval(intervalRef.current)
29  }, [])
30  
31  return (
32    <div>
33      <p>{time}s</p>
34      <button onClick={start}>Start</button>
35      <button onClick={stop}>Stop</button>
36      <button onClick={reset}>Reset</button>
37    </div>
38  )
39}

Tracking Mount State

jsx
1function useIsMounted() {
2  const isMounted = useRef(true)
3  
4  useEffect(() => {
5    return () => {
6      isMounted.current = false
7    }
8  }, [])
9  
10  return isMounted
11}
12
13// Prevent state updates on unmounted component
14function AsyncComponent() {
15  const [data, setData] = useState(null)
16  const isMounted = useIsMounted()
17  
18  useEffect(() => {
19    fetchData().then(result => {
20      if (isMounted.current) {
21        setData(result)
22      }
23    })
24  }, [])
25}

Measuring Elements

jsx
1function MeasuredBox() {
2  const boxRef = useRef(null)
3  const [dimensions, setDimensions] = useState({ width: 0, height: 0 })
4  
5  useEffect(() => {
6    if (boxRef.current) {
7      const { width, height } = boxRef.current.getBoundingClientRect()
8      setDimensions({ width, height })
9    }
10  }, [])
11  
12  return (
13    <div ref={boxRef} style={{ width: '50%', padding: 20 }}>
14      <p>Width: {dimensions.width}px</p>
15      <p>Height: {dimensions.height}px</p>
16    </div>
17  )
18}

Callback Refs

For more control over when refs are set:

jsx
1function CallbackRefExample() {
2  const [height, setHeight] = useState(0)
3  
4  const measuredRef = useCallback(node => {
5    if (node !== null) {
6      setHeight(node.getBoundingClientRect().height)
7    }
8  }, [])
9  
10  return (
11    <div ref={measuredRef}>
12      <p>Height: {height}px</p>
13    </div>
14  )
15}

Forwarding Refs

Pass refs to child components:

jsx
1import { forwardRef, useRef } from 'react'
2
3// Child component accepts forwarded ref
4const FancyInput = forwardRef((props, ref) => {
5  return <input ref={ref} className="fancy" {...props} />
6})
7
8// Parent can now access child's DOM element
9function Parent() {
10  const inputRef = useRef(null)
11  
12  const focus = () => inputRef.current?.focus()
13  
14  return (
15    <div>
16      <FancyInput ref={inputRef} placeholder="Type here" />
17      <button onClick={focus}>Focus Input</button>
18    </div>
19  )
20}

useRef vs useState

useRefuseState
Doesn't trigger re-renderTriggers re-render
Value persists across rendersValue persists across renders
Mutable (.current)Immutable (use setter)
Sync updatesBatched updates
Good for DOM refs, timers, previous valuesGood for UI state

useRef is essential for working with DOM elements and mutable values!