Home

Run React Effect Hook only Once in Strict Mode

Running React in strict mode with Next.js can lead to useEffect callbacks with zero dependencies to run twice in development. Here’s a way around that.

You may have noticed that with newer Next.js projects, a useEffect hook runs twice. New Next.js projects now run in strict mode, which is a feature of React 18.

A breaking change that came with React 18 was that components are mounted, unmounted, and remounted in development mode. This causes an effect hook (with specified dependencies that don’t cause additional renders) to run twice.

Effect Hook that Runs Twice

Here’s an example of a component with an effect hook that runs twice:

import { useEffect, useState, useRef } from "react";
import styles from "../styles/Home.module.css";

export const SimpleEffect = () => {
const [timesRun, setTimesRun] = useState(0);
const counter = useRef < number > 0;

useEffect(() => {
counter.current += 1;
setTimesRun(counter.current);
}, []);

return (
<p className={styles.description}>
<code className={styles.code}>SimpleEffect</code> called:{" "}
<code className={styles.code}>{timesRun}</code>
</p>
);
};

This is a simple counter that gets incremented every time the effect hook runs. This will result in 2 being rendered to the screen when running in development mode.

note

I’m using a ref object here because I can be sure that the value increments. Because state changes are set asynchronously, I can’t ensure I’ll get 2 consistently, even thought the hook is run twice.

Using a Reference to Ensure Hook only Runs Once

To ensure this hook only runs once in development mode, we can add another reference object that tracks whether the callback to useEffect has been called. Something like this:

import { useEffect, useState, useRef } from "react";
import styles from "../styles/Home.module.css";

export const EffectRunOnce = () => {
const [timesRun, setTimesRun] = useState(0);
const counter = useRef < number > 0;
const effectCalled = useRef < boolean > false;

useEffect(() => {
if (effectCalled.current) return;
counter.current += 1;
setTimesRun(counter.current);
effectCalled.current = true;
}, []);

return (
<p className={styles.description}>
<code className={styles.code}>EffectRunOnce</code> called:{" "}
<code className={styles.code}>{timesRun}</code>
</p>
);
};

Now we have an efectCalled reference that starts as false. It gets set to true the first time the effect runs. Every subsequent time, we exit early from the effect callback.

Single Effect Run Demo

Here's a demo in which you can see both components in action.

Let's Connect

Keep Reading

Open External next/link Links in a New Tab

Add a component that dynamically swaps between next/link and a native anchor tag, and decides how to write the target attribute, all based on the href property.

Jun 30, 2022

Render XML Page with Next.js

Learn how to generate an XML page with Next.js.

Jul 23, 2022

Avoiding an Infinite Loop in the useEffect Hook

It’s surprisingly easy to create infinite loops in React.

Jun 24, 2022