Self-paced

Explore our extensive collection of courses designed to help you master various subjects and skills. Whether you're a beginner or an advanced learner, there's something here for everyone.

Bootcamp

Learn live

Join us for our free workshops, webinars, and other events to learn more about our programs and get started on your journey to becoming a developer.

Upcoming live events

Learning library

For all the self-taught geeks out there, here is our content library with most of the learning materials we have produced throughout the years.

It makes sense to start learning by reading and watching videos about fundamentals and how things work.

Search from all Lessons


← Back to Lessons

The useRef Hook in React: Managing DOM References and Persistent Values

What is useRef?
Key Use Cases for useRef

The useRef hook is a powerful tool in React's hooks API that provides a way to create a mutable reference that persists across renders. Unlike state variables, changing a ref's value doesn't trigger a re-render, making it ideal for certain use cases that we'll explore in this lesson.

What is useRef?

The useRef hook returns a mutable object with a .current property that is initialized to the provided argument. This object persists for the full lifetime of the component.

1const refContainer = useRef(initialValue);

The object returned by useRef has two important characteristics:

  1. It's mutable: You can modify its .current property
  2. It's persistent: It keeps the same reference across re-renders

Key Use Cases for useRef

1. Accessing DOM Elements

One of the most common uses for useRef is to access and manipulate DOM elements directly:

1import React, { useRef, useEffect } from 'react'; 2 3function AutoFocusInput() { 4 // Create a ref 5 const inputRef = useRef(null); 6 7 // Use the ref in useEffect 8 useEffect(() => { 9 // Focus the input element when component mounts 10 inputRef.current.focus(); 11 }, []); 12 13 return <input ref={inputRef} type="text" />; 14}

In this example, we:

  1. Create a ref with useRef(null)
  2. Attach it to an input element with the ref attribute
  3. Access the actual DOM node via inputRef.current
  4. Call the native focus() method on the input element

2. Storing Persistent Values

Another powerful use of useRef is to keep a value that persists across renders without causing re-renders when changed:

1import React, { useRef, useEffect, useState } from 'react'; 2 3function IntervalCounter() { 4 const [count, setCount] = useState(0); 5 6 // Store interval ID in a ref so it persists across renders 7 const intervalIdRef = useRef(null); 8 9 useEffect(() => { 10 // Set up the interval 11 intervalIdRef.current = setInterval(() => { 12 setCount(prevCount => prevCount + 1); 13 }, 1000); 14 15 // Clean up the interval 16 return () => { 17 clearInterval(intervalIdRef.current); 18 }; 19 }, []); // Empty dependency array means this runs once on mount 20 21 return <div>Count: {count}</div>; 22}

Here, we use a ref to store the interval ID so we can clear it when the component unmounts. Using a ref is better than a state variable because:

  • We don't need to re-render when the interval ID changes
  • We don't need the interval ID in the JSX, just in the cleanup function

3. Tracking Previous Values

You can use refs to track previous values of props or state:

1import React, { useRef, useEffect, useState } from 'react'; 2 3function TrackChanges({ value }) { 4 const prevValueRef = useRef(); 5 const [hasChanged, setHasChanged] = useState(false); 6 7 useEffect(() => { 8 // Check if value has changed since last render 9 if (prevValueRef.current !== undefined && prevValueRef.current !== value) { 10 setHasChanged(true); 11 } 12 13 // Update ref to current value for next render 14 prevValueRef.current = value; 15 }, [value]); 16 17 return ( 18 <div> 19 <p>Current value: {value}</p> 20 {hasChanged && <p>The value has changed!</p>} 21 </div> 22 ); 23}

This pattern is useful when you need to compare current and previous values to perform side effects.

useRef vs. useState

It's important to understand when to use useRef versus useState:

FeatureuseRefuseState
Triggers re-render when updatedNoYes
Persists between rendersYesYes
Access patternref.current[state, setState]
Best forDOM refs, storing values without re-rendersUI state that needs to be displayed

Use useRef when:

  • You need to access DOM elements directly
  • You need to store a value that shouldn't trigger a re-render when it changes
  • You need to keep track of mutable values that don't affect the UI directly

Use useState when:

  • The value needs to be displayed in the UI
  • Changes to the value should cause the component to re-render
  • You need React to track changes to the value

Common Patterns and Best Practices

Handling Initialization

1// Lazy initialization 2const videoRef = useRef(null); 3 4// With initial value 5const countRef = useRef(0);

Accessing DOM Nodes

1// Basic DOM access 2const imageRef = useRef(null); 3 4// Later in the code: 5if (imageRef.current) { 6 const dimensions = { 7 width: imageRef.current.clientWidth, 8 height: imageRef.current.clientHeight 9 }; 10}

Avoiding Common Pitfalls

  1. Don't use refs for values that should update the UI:
1// Incorrect usage - UI won't update when count changes 2const countRef = useRef(0); 3const handleClick = () => { 4 countRef.current += 1; 5 // No re-render occurs! 6}; 7 8// Correct usage 9const [count, setCount] = useState(0); 10const handleClick = () => { 11 setCount(count + 1); 12 // Component will re-render 13};
  1. Always check if the ref is attached before using it:
1// Good practice 2const inputRef = useRef(null); 3 4const focusInput = () => { 5 // Check if ref is attached 6 if (inputRef.current) { 7 inputRef.current.focus(); 8 } 9};

Practical Examples

1. Video Player Controls

1import React, { useRef, useState } from 'react'; 2 3function VideoPlayer() { 4 const videoRef = useRef(null); 5 const [isPlaying, setIsPlaying] = useState(false); 6 7 const handlePlayPause = () => { 8 if (isPlaying) { 9 videoRef.current.pause(); 10 } else { 11 videoRef.current.play(); 12 } 13 setIsPlaying(!isPlaying); 14 }; 15 16 return ( 17 <div> 18 <video 19 ref={videoRef} 20 src="https://example.com/video.mp4" 21 /> 22 <button onClick={handlePlayPause}> 23 {isPlaying ? 'Pause' : 'Play'} 24 </button> 25 </div> 26 ); 27}

2. Scroll Position Tracker

1import React, { useRef, useEffect, useState } from 'react'; 2 3function ScrollTracker() { 4 const containerRef = useRef(null); 5 const [scrollPercentage, setScrollPercentage] = useState(0); 6 7 useEffect(() => { 8 const container = containerRef.current; 9 10 const handleScroll = () => { 11 if (container) { 12 const scrollTop = container.scrollTop; 13 const scrollHeight = container.scrollHeight - container.clientHeight; 14 const percentage = (scrollTop / scrollHeight) * 100; 15 setScrollPercentage(Math.round(percentage)); 16 } 17 }; 18 19 container.addEventListener('scroll', handleScroll); 20 return () => { 21 container.removeEventListener('scroll', handleScroll); 22 }; 23 }, []); 24 25 return ( 26 <div> 27 <div 28 ref={containerRef} 29 style={{ 30 height: '300px', 31 overflow: 'auto', 32 border: '1px solid #ccc' 33 }} 34 > 35 {/* Content here */} 36 <div style={{ height: '1000px' }}> 37 Scroll down to see the percentage change 38 </div> 39 </div> 40 <p>Scroll position: {scrollPercentage}%</p> 41 </div> 42 ); 43}

When Not to Use useRef

While useRef is powerful, it's not always the right tool:

  1. Don't use it to bypass React's data flow

    • React's unidirectional data flow is a feature, not a limitation
    • Using refs to manage what should be state can lead to hard-to-debug issues
  2. Don't use it for values that affect rendering

    • If a value should trigger a re-render when it changes, use state instead
  3. Don't overuse imperative code

    • React is declarative by design
    • Only use imperative DOM manipulation when there's no declarative alternative

Advanced useRef Patterns

Callback Refs

For more dynamic ref assignment, you can use a callback instead of the ref attribute:

1function MeasureExample() { 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 <> 12 <h1 ref={measuredRef}>Hello, world</h1> 13 <p>The above header is {Math.round(height)}px tall</p> 14 </> 15 ); 16}

Forwarding Refs

When creating component abstractions, you can forward refs to inner elements:

1const FancyButton = React.forwardRef((props, ref) => ( 2 <button ref={ref} className="fancy-button" {...props}> 3 {props.children} 4 </button> 5)); 6 7// Usage 8function App() { 9 const buttonRef = useRef(null); 10 11 const handleClick = () => { 12 buttonRef.current.focus(); 13 }; 14 15 return ( 16 <> 17 <FancyButton ref={buttonRef}>Click me!</FancyButton> 18 <button onClick={handleClick}>Focus the fancy button</button> 19 </> 20 ); 21}

Conclusion

The useRef hook is a versatile tool in the React hooks arsenal. It provides a way to:

  • Access DOM elements directly when necessary
  • Store mutable values that persist across renders
  • Keep track of information without triggering re-renders

By understanding when and how to use useRef, you can build more efficient React components and solve problems that would be difficult with state alone. Remember that while imperative code has its place, React is fundamentally a declarative library, so use useRef judiciously and preferably for cases where a declarative solution doesn't exist or would be overly complex.