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:

src/components/Counter.jsx
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;
src/App.jsx
// ....
<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.

Other things to read

Popular

Previous/Next