1

I have a react page built with React and Next.js that looks like this.

import Head from 'next/head';
import { useEffect, useState } from 'react';
import Header from '../components/Header';

export default function Home() {
    const ticksPerSecond = 10;

    const [points, setPoints] = useState(10);
    const [pointsPerSecond, setPointsPerSecond] = useState(2);

    useEffect(() => {
        setInterval(() => {
            setPoints((points) => points + pointsPerSecond / ticksPerSecond);
        }, 1000 / ticksPerSecond);
    }, []);

    return (
        <>
            <Header points={points} pointsPerSecond={pointsPerSecond} />
        </>
    );
}

This code works as intended. Every second, points will increase by pointsPerSecond, and the state will be updated 10 times a second (as determined by ticksPerSecond).

My issue comes from eslint which warns me:

React Hook useEffect has a missing dependency: 'pointsPerSecond'. Either include it or remove the dependency array. You can also replace multiple useState variables with useReducer if 'setPoints' needs the current value of 'pointsPerSecond'.eslintreact-hooks/exhaustive-deps

Having read that warning, I have tried many different solutions to appease the eslint gods in the sky, but the best I can do it replace the warnings with different warnings. Specifically, I have tried the solutions from this question which looks like the same problem. However, none of those solutions suppress the warning.

Julian Lachniet
  • 223
  • 4
  • 25

3 Answers3

1

I recommend adding pointsPerSecond to the dependency array and then making sure to return a cleanup function from your effect to clear the existing interval. This assumes you want to stop any existing interval and start a new one whenever pointsPerSecond changes. If you don't include pointsPerSecond in the dependency array, you'll be working with a stale version of that dependency.

import Head from 'next/head';
import { useEffect, useState } from 'react';
import Header from '../components/Header';

const ticksPerSecond = 10;

export default function Home() {
    const [points, setPoints] = useState(10);
    const [pointsPerSecond, setPointsPerSecond] = useState(2);

    useEffect(() => {
        const interval = setInterval(() => {
            setPoints((points) => points + pointsPerSecond / ticksPerSecond);
        }, 1000 / ticksPerSecond);

        return () => { clearInterval(interval) }
    }, [pointsPerSecond]);

    return (
        <>
            <Header points={points} pointsPerSecond={pointsPerSecond} />
        </>
    );
}

There are a couple reasons I almost always recommend against silencing linting errors for hook dependency arrays:

  • It's almost always the case that you want your hook to have the most current value.
  • If you silence the lint warnings for the dependency array, then you won't get these warnings for future dependencies for which you might really want to have the warnings.
Nick
  • 16,066
  • 3
  • 16
  • 32
0

A couple of changes I would do.

  1. Don't forget to clearInterval on unmouted,
  2. Do a mounted check before calling setState's.
  3. Add the dependancy esLint is warning you about.

Below is an example.

useEffect(() => {
  let mounted = true;
  const i = setInterval(() => {
    if (mounted) setPoints((points) => points + pointsPerSecond / ticksPerSecond);
  }, 1000 / ticksPerSecond);
  return () => { clearInterval(i); mounted = false; }
}, [pointsPerSecond]);
Keith
  • 22,005
  • 2
  • 27
  • 44
  • This is interesting, what does the `mounted` variable offer that clearing the interval doesn't? – Nick Jul 27 '21 at 22:56
  • Because pointsPerSecond can change, wouldn't this cause two intervals to run at once? – Julian Lachniet Jul 27 '21 at 22:58
  • @JulianLachniet No, that's why we do clearInterval. – Keith Jul 27 '21 at 22:59
  • @Nick It's just in case the interval still fires after un-mounting, it generally doesn't cause issues, apart from getting warnings in the console from React about calling setState on an un-mounted component. For something trivial like this it's maybe not an issue, but any form of async code I now just do a mounted check anyway, I just don't like console clutter. :). – Keith Jul 27 '21 at 23:12
-1

Try adding pointsPerSecond on dependency array

  useEffect(() => {
    setInterval(() => {
      setPoints((points) => points + pointsPerSecond / ticksPerSecond);
    }, 1000 / ticksPerSecond);
  }, [pointsPerSecond]);

Or you can just ignore the warning using // eslint-disable-next-line react-hooks/exhaustive-deps

 useEffect(() => {
    setInterval(() => {
      setPoints((points) => points + pointsPerSecond / ticksPerSecond);
    }, 1000 / ticksPerSecond);
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);