We use cookies to enhance your experience on the site
CodeWorlds

Storing Values Between Renders with useRef

On our cosmic journey through React, sometimes we need to store information that persists between component renders but shouldn't cause a re-render. The useRef hook is like an escape pod - it stores data you can retrieve at any moment, regardless of how many times your spaceship (component) gets rebuilt.

What is useRef?

The

useRef
hook creates a mutable reference object whose
.current
property is initialized with the passed value. An important feature of useRef is that:

  1. The value stored in the reference remains the same between renders
  2. Changing the ref value does NOT cause the component to re-render
  3. Acts as a "memory store" that you can reference at any point in the component's lifecycle

useRef vs useState - key differences

Let's compare two key methods of storing data in components:

1function SpaceshipDashboard() {
2  // State - changes cause re-rendering
3  const [speed, setSpeed] = useState(0);
4
5  // Reference - changes do NOT cause re-rendering
6  const previousSpeedRef = useRef(0);
7
8  // When speed changes, we save the previous value
9  useEffect(() => {
10    previousSpeedRef.current = speed;
11  }, [speed]);
12
13  const increaseSpeed = () => {
14    setSpeed(speed + 10); // This will cause a re-render
15  };
16
17  return (
18    <div>
19      <p>Current velocity: {speed} km/h</p>
20      <p>Previous speed: {previousSpeedRef.current} km/h</p>
21      <button onClick={increaseSpeed}>Increment speed</button>
22    </div>
23  );
24}

Common use cases for useRef

1. Storing previous values

One of the most popular uses of useRef is remembering previous values between renders:

1function AsteroidTracker({ asteroid }) {
2  const [position, setPosition] = useState({ x: 0, y: 0 });
3  const previousPositionRef = useRef({ x: 0, y: 0 });
4
5  useEffect(() => {
6    // We save the current position as previous before the next update
7    previousPositionRef.current = position;
8  }, [position]);
9
10  // We calculate how far the asteroid has moved
11  const distanceMoved = Math.sqrt(
12    Math.pow(position.x - previousPositionRef.current.x, 2) +
13    Math.pow(position.y - previousPositionRef.current.y, 2)
14  );
15
16  return (
17    <div>
18      <p>Current position: ({position.x}, {position.y})</p>
19      <p>Previous position: ({previousPositionRef.current.x}, {previousPositionRef.current.y})</p>
20      <p>Distance traveled: {distanceMoved}</p>
21    </div>
22  );
23}

2. Counters and instance values that don't affect rendering

1function MissionControl() {
2  const missionTimeRef = useRef(0);
3  const timerIdRef = useRef(null);
4
5  // Starting mission timer
6  const startMissionTimer = () => {
7    if (timerIdRef.current !== null) return; // Avoiding multiple starts
8
9    timerIdRef.current = setInterval(() => {
10      missionTimeRef.current += 1;
11      console.log(\`Mission time: \${missionTimeRef.current} seconds\`);
12      // Note that this does NOT cause component re-rendering
13    }, 1000);
14  };
15
16  // Stopping mission timer
17  const stopMissionTimer = () => {
18    clearInterval(timerIdRef.current);
19    timerIdRef.current = null;
20  };
21
22  // Cleaning up on component unmount
23  useEffect(() => {
24    return () => {
25      if (timerIdRef.current !== null) {
26        clearInterval(timerIdRef.current);
27      }
28    };
29  }, []);
30
31  return (
32    <div>
33      <button onClick={startMissionTimer}>Start mission</button>
34      <button onClick={stopMissionTimer}>End mission</button>
35      <button onClick={() => alert(\`Mission time: \${missionTimeRef.current} seconds\`)}>
36        Show mission time
37      </button>
38    </div>
39  );
40}

3. Accessing DOM elements

1function SpaceshipScanner() {
2  const scannerInputRef = useRef(null);
3
4  const focusScannerInput = () => {
5    // Direct access to DOM element
6    scannerInputRef.current.focus();
7  };
8
9  return (
10    <div>
11      <input
12        ref={scannerInputRef}
13        type="text"
14        placeholder="Enter coordinates..."
15      />
16      <button onClick={focusScannerInput}>Activate scanner</button>
17    </div>
18  );
19}

Remember the rules for using useRef

  1. Don't overuse it: Use useRef only when the value truly shouldn't cause a re-render
  2. Avoid direct DOM mutations: Although useRef gives you access to DOM elements, use it sparingly and in accordance with React's philosophy
  3. Remember to clean up: If you use useRef to store timers or subscriptions, remember to clean them up in the useEffect cleanup function

Summary

The useRef hook is invaluable when you need to:

  • Store a value between renders without causing a re-render
  • Track previous values
  • Get direct access to DOM elements
  • Store timer identifiers or other instance values

Like an escape pod in space, useRef stores valuable data that may be needed at critical moments in your component's mission.

Go to CodeWorlds