What’s the difference between using a ref and a variable to keep track of a value in React?
Table of contents
Sometimes, you want to keep track of a value in React, but you don’t want to store it in the state because it will cause extra renders when it changes. The recommended method (for a functional component) is to use the useRef
hook to store the value there.
In the following example, I want to run the effect when the state
changes, but not the first time the component mounts. For this reason, I use a firstTime
ref
that initially is set to true
and run the effect only when the firstTime.current
is false
:
import React, { useState, useEffect, useRef } from "react";
const Component = () => {
const [state, setState] = useState(0);
const firstTime = useRef(true);
useEffect(() => {
if (!firstTime.current) {
// Run the effect.
} else {
firstTime.current = false;
}
}, [state]);
return <div></div>;
};
export default Component;
The syntax is a bit ugly because you have to use the .current
property, and you might be tempted to use a regular variable instead of the useRef
hook, as shown in the next example.
import React, { useState, useEffect } from "react";
let firstTime = true;
const Component = () => {
const [state, setState] = useState(0);
useEffect(() => {
if (!firstTime) {
// Run the effect.
} else {
firstTime = false;
}
}, [state]);
return <div></div>;
};
export default Component;
Differences
As long as the component is a singleton—meaning that you use only one instance of the component in your application—both methods do the same thing. Multiple instances, though, which is really common, share the same variable! As a result, you should avoid using a regular variable because it can get pretty bad. The following example illustrates the problem:
import React, { useState } from "react";
let counterOutside = 0;
const Counter = () => {
const [counter, setCounter] = useState(0);
console.log(counterOutside);
return (
<p>
The counter is {counter}{" "}
<button
onClick={() => {
setCounter(counter + 1);
counterOutside = counterOutside + 1;
}}
>
+
</button>
</p>
);
};
export default Counter;
// ....
<div>
<Counter />
<Counter />
</div>
If you press the button inside one counter, it prints “1 1” where it should print “1 0”, “2, 2” where it should print “2 0”, and so on.
Links
- Hooks FAQ: Is there something like instance variables?
useRef
in Hooks API.- The same question in a GitHub issue.
Other things to read
Popular
- Reveal animations on scroll with react-spring
- Gatsby background image example
- Extremely fast loading with Gatsby and self-hosted fonts