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.
The
useRef hook creates a mutable reference object whose .current property is initialized with the passed value. An important feature of useRef is that: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}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}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}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}The useRef hook is invaluable when you need to:
Like an escape pod in space, useRef stores valuable data that may be needed at critical moments in your component's mission.