# useRef explained
The useRef
hook is a fundamental React hook that allows you to create a mutable reference that persists across component re-renders without causing the component to re-render when its value changes. Here's a comprehensive breakdown:
# Key Characteristics of useRef
📚 Persistent Reference
useRef
returns a mutable ref object that remains consistent throughout the component's lifecycle- The
.current
property of the ref can be modified without triggering a re-render - Commonly used for:
- Accessing DOM elements directly
- Storing mutable values that don't require re-rendering
- Tracking previous values
- Storing timer or interval references
# Basic Syntax
const refContainer = useRef(initialValue);
# Common Use Cases
# 1. Accessing DOM Elements
See the code at file /components/accesingDOM.jsx (opens new window) at branch allrepos
import React, { useState, useRef } from "react";
import styles from '@/components/Home.module.css'
export default function TextInputWithFocusButton() {
const [username, setUsername] = useState("");
const inputRef = useRef(null);
const focusInput = () => {
// Directly access and manipulate the DOM element
inputRef.current.focus();
};
return (
<>
<input
ref={inputRef}
onChange={(e) => setUsername(e.target.value)}
className={styles.input}
type="text"
/>
<button onClick={focusInput} className={styles.button}>Focus the input</button>
<p className={styles.title}>Typed username: {username}</p>
</>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
The ref={inputRef}
attribute in the JSX code at line 16 is used to attach a reference to the DOM element,
allowing you to directly access and manipulate the element in your React component.
The inputRef.current
used at line 10 refers to the DOM element that inputRef
is attached to.
The inputRef.current.focus() (opens new window)
method is called directly on the DOM element via inputRef.current
!.
When the user clicks the button, the focusInput
function is called, and the cursor is moved to the input field:
# 2. Storing Mutable Values Without Re-renders
See file ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/components/usereftimer.jsx (opens new window) at branch allrepos
:
import React, { useState, useRef } from 'react';
import styles from '@/components/counters.module.css'
function Stopwatch() {
// State to control running status (will cause re-renders)
const [isRunning, setIsRunning] = useState(false);
// Refs to store mutable values without re-rendering
const startTimeRef = useRef(null);
const elapsedTimeRef = useRef(0);
const intervalRef = useRef(null);
// Function to start the stopwatch
const startStopwatch = () => {
if (!isRunning) { // only start if not already running
// Record the start time, accounting for previous elapsed time
startTimeRef.current = Date.now() - elapsedTimeRef.current;
// Set up an interval to update elapsed time
intervalRef.current = setInterval(() => {
// Directly mutate the ref without causing re-render
elapsedTimeRef.current = Date.now() - startTimeRef.current;
}, 10); // Update every 10 milliseconds for precision
setIsRunning(true);
}
};
// Function to stop the stopwatch
const stopStopwatch = () => {
if (isRunning) {
// Clear the interval
clearInterval(intervalRef.current);
// Stop tracking time
setIsRunning(false);
}
};
// Function to reset the stopwatch
const resetStopwatch = () => {
// Clear any running interval
clearInterval(intervalRef.current);
// Reset all tracking values
startTimeRef.current = null;
elapsedTimeRef.current = 0;
// Update running state
setIsRunning(false);
};
// Format time for display
const formatTime = (milliseconds) => {
const totalSeconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
const ms = Math.floor((milliseconds % 1000) / 10);
return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}.${ms.toString().padStart(2, '0')}`;
};
// Component to display current time (will re-render with state changes)
return (
<div>
<div style={{ fontSize: '2rem', marginBottom: '20px' }}>
{formatTime(elapsedTimeRef.current)}
</div>
<div>
<button
onClick={startStopwatch}
disabled={isRunning}
className={styles.counter}
>Start</button>
<button
onClick={stopStopwatch}
disabled={!isRunning}
className={styles.counter}
>Stop</button>
<button onClick={resetStopwatch} className={styles.counter} >Reset</button>
</div>
</div>
);
}
export default Stopwatch;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
- When you click the start button, the stopwatch begins counting up but does not cause a re-render. We see the
00:00.00
time displayed. - When you click the stop button the stopwatch stops counting but due to the state variable
isRunning
changing, the component re-renders. We see the time displayed at the moment the stopwatch was stopped.
# 3. Tracking Previous Values
See the code at file ULL-MII-SYTWS-2425/nextra-casiano-rodriguez-leon-alu0100291865/components/userefTracker.jsx (opens new window) at branch allrepos
:
import React, { useState, useRef, useEffect } from "react";
import styles from "@/components/UserRepos.module.css";
export default function PreviousValueTracker() {
const [count, setCount] = useState(0);
const [inputValue, setInputValue] = useState('');
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
}, [count]);
const previousCount = prevCountRef.current;
const handleIncrement = () => {
setCount(prevCount => prevCount + 1);
};
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
// Handler for setting count from input
const handleSetCount = () => {
const newCount = parseInt(inputValue, 10);
if (!isNaN(newCount)) {
setCount(newCount);
setInputValue(''); // Clear input after setting
}
};
return (
<>
<ul className={styles.list}>
<li>Current: {count}</li>
<li>Previous: {previousCount}</li>
<li><button onClick={handleIncrement} className={styles.button}>Increment</button></li>
<li> Change the count:
<input
type="text"
onChange={handleInputChange}
className={styles.input}
/>
</li>
<li><button onClick={handleSetCount} className={styles.button}>Set Count</button>
</li>
</ul>
</>
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
The inputValue
state declared at line 6 holds the current value of the input
field, and setInputValue
is used at line 20 (coming from 42)
to update this value as the user types.
The combination of useRef
and useEffect
allows the component to keep track of the previous value of count
without causing additional re-renders.
Now when you click the Increment button, the count increases, and the previous count is displayed. If you write a number in the input field and click the Set Count button, the count is set to the input value, and the previous count is displayed.
# Key Differences from useState
useRef
does not cause re-renders when its value changesuseState
triggers a re-render when its state is updated- Use
useRef
for values you want to mutate without affecting the component's rendering
Best Practices
- Use
useRef
for values that don't impact the visual output - Avoid overusing refs for state management
- Prefer controlled components and state when possible
- Be cautious when directly manipulating DOM elements
Potential Gotchas
- The ref's
.current
property is mutable - Changing
.current
doesn't trigger a re-render - Refs are reset when components unmount
When to Use `useRef`
- Storing references to DOM elements
- Keeping mutable values without re-renders
- Managing timers and intervals
- Implementing imperative animations
- Tracking values between renders without causing re-renders
By understanding useRef
, you can create more flexible and performant React components, especially when you need to work with direct DOM manipulation or store mutable values efficiently.