Infinite Loop over array and change state

Viewed 40

I want to loop over an array of 3 months ["1", "2", "3"] and show the current month every 2 seconds. I tried using useEffet, useState and setInterval to build a cycle but i get an unwanted behavior.

Here is a sandbox i made : https://codesandbox.io/s/autumn-voice-y6kdpb?file=/src/App.js

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

export default function App() {
  const months = ["1", "2", "3"];
  let count = 0;
  const [month, setMonth] = useState(months[count]);
  useEffect(() => {
    function cycleArray() {
      setMonth(months[count]);
      // increment our counter
      count++;
      // reset counter if we reach end of array
    }
    if (count === months.length) {
      count = 0;
    }
    setInterval(cycleArray, 1000);
  }, [count]);
  return (
    <div className="App">
      <h1>Loop over months (next month every 1 second) </h1>
      <h2>{month}</h2>
    </div>
  );
}

What am I doing wrong ?

2 Answers

You should be careful when using things like useInterval inside the useEffect hook. By default the react strict mode is activated in dev mode. This means certain hooks and callbacks are called twice to make the dev aware of not cleaned up side effects inside those hooks and callbacks. In such case your useEffect should return a cleanup function to remove the interval for rerrendering purposes.

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

export default function App() {
  const months = ["1", "2", "3"];
  const [count, setCount] = useState(0);

  // hook only runs at first render of component and start our interval of 1 sec
  useEffect(() => {
    const interval = setInterval(() => {
      // increment our counter
      setCount((e) => e + 1);     
    }, 1000);

    // our cleanup function, so no interval is left behind when rendering the component new
    return () => {
      clearInterval(interval);
    };
  }, []);

  // our hook that is subscribed to the count state; loops the count back around
  useEffect(() => {
    if (count >= months.length) {
      setCount(0);
    }
  }, [count]);

  return (
    <div className="App">
      <h1>Loop over months (next month every 1 second) </h1>
      <h2>{months[count]}</h2>
    </div>
  );
}

https://codesandbox.io/s/wild-platform-ufegmv?file=/src/App.js

It is a good idea to report what "unwanted behavior" is. Just from a quick view, you are using count variable when it isn't necessary. Use the month state variable as the count. But the way you have the code set up, setInterval is unreliable just based on how javascript is written, so letting it run long enough, displaying the months will be irratic.

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

export default function App() {
  const months = [1, 2, 3];
  const [month, setMonth] = useState(0);
  useEffect(() => {
    function cycleArray() {

      // increment our counter
      setMonth(month + 1);

      // reset counter if we reach end of array
    }
    if (month === months.length) {
      setMonth(0);
    }
    setInterval(cycleArray, 1000);
  }, [month]);
  return (
    <div className="App">
      <h1>Loop over months (next month every 1 second) </h1>
      <h2>{month}</h2>
    </div>
  );
}

Related