0

Imagine I have an array of objects like this:

const items = [
    {name: "item 0"},
    {name: "item 1"},
    {name: "item 2"}
]

And a component like this:

const [currentItemIndex, setCurrentItemIndex] = useState(0)

setInterval(function () {
   if (currentItemIndex < 3) {
      setCurrentItemIndex(currentItemIndex + 1);
   } else {
      clearInterval();
   }
}, 5000);
   
return (
    <div>
        <p> {items[currentItemIndex].name} <p>
    </div>
)

I want to update currentItemIndex every 5 seconds so the div shows next item, and if it reaches the end, it should start over: 0, 1, 2, 0, 1, 2, 0, 1, 2 ....

The problem is: it loops correctly the first time, but after reaching the end, it shows the first item for a fraction of a second and then goes to the last item: 0, 1, 2, 0 (really quick to) 2.

What I'm doing wrong?
I searched for this, but every article is talking about "how to prevent infinite loops", not "how to use them"!

Shahriar
  • 1,855
  • 2
  • 21
  • 45
  • instead of `setCurrentItemIndex(currentItemIndex + 1)` can you try `setCurrentItemIndex( val => (val + 1))` – Beginner Nov 26 '21 at 16:16
  • you need to use `useEffect` to handle effects like `setTimeout` - e.g. https://stackoverflow.com/questions/57542264/how-to-setinterval-for-every-5-second-render-with-react-hook-useeffect-in-react – andy mccullough Nov 26 '21 at 16:18
  • 4
    First, your use of [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval) is incorrect and doesn't do anything. Second, your loop will never start back at the begining as you never reset your counter. – Randy Casburn Nov 26 '21 at 16:18
  • 1
    you need something that will move `currentItemIndex` back to zero and then you can keep increamenting it again. So for that you can add check like this `setCurrentItemIndex((currentItemIndex + 1) % items.length);` – Saad Mehmood Nov 26 '21 at 16:21
  • @SaadMehmood Nice idea. – Shahriar Nov 26 '21 at 16:33

3 Answers3

2

You can do it with useEffect and with setTimeout like this:

const items = [{ name: "item 0" }, { name: "item 1" }, { name: "item 2" }];

const App = () => {
  const [currentItemIndex, setCurrentItemIndex] = React.useState(0);

  React.useEffect(() => {
    const id = setTimeout(
      () => setCurrentItemIndex((currentItemIndex + 1) % items.length),
      1000
    );
    return () => {
      clearInterval(id); // removes React warning when gets unmounted
    };
  }, [currentItemIndex]);

  return (
    <div>
      <p> {items[currentItemIndex].name} </p>
    </div>
  );
};

// export default App;

ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Konstantin Modin
  • 1,313
  • 7
  • 12
1

I see a few problems with this logic that you created.

First of all, clearInterval needs to be provided a variable that you created when creating the setInterval function (as you can see here), so this clearInterval() is doing nothing.

Besides that, you do not want to clear your timer when currentItemIndex reaches its threshold, but instead you want it to go back to zero.

In React, we usually use useEffect for timing events as you need to clear them on unmount so you don't keep them running after your component is unmounted. Something like this:

useEffect(() => {
    const timer = setInterval(yourFunction);
    return () => clearInterval(timer);
}, [])
Daniel Baldi
  • 794
  • 1
  • 9
0

You can use this approach if you want to. You create a function using which will set state after 5 seconds using timeout. Once the state is set you again call the same method. For sake of react you call this method once using useEffect.

import { useEffect, useState } from "react";


const items = [{ name: "item 0" }, { name: "item 1" }, { name: "item 2" }];


export default function App() {
  const [currentItemIndex, setCurrentItemIndex] = useState(0);

  useEffect(() => {
    incrementAfterDelay(1000, 1, 2);
  }, []);

  const incrementAfterDelay = (delay, step, lastStep) => {
    setTimeout(
      () =>
        setCurrentItemIndex((value) => {
          return (value < lastStep)? value + step : 0
        }, incrementAfterDelay(delay, step, lastStep)),
      delay
    );
  };

  return (
    <div>
      <p> {items[currentItemIndex].name} </p>
    </div>
  );
}
Beginner
  • 182
  • 2
  • 14