Adam
Adam

Reputation: 3128

Displaying a different values every X seconds using React hooks

I want to display "Orange" for 2 seconds, "Kiwi" or 1 second and "Mango" for 3 seconds. Here's my attempt which is displaying a still "Orange: 2000" while I am expecting it to flip based on the number of specified seconds. What am I missing?

import React, { useState, useEffect } from 'react';

const IntervalExample = () => {
    const fruits = [
        {id: 1, name: "Orange", duration: 2000},
        {id: 2, name: "Kiwi", duration: 1000},
        {id: 3, name: "Mango", duration: 3000},
    ]

    const [index, setIndex] = useState(0);
    const [currentFruit, setCurrentFruit] = useState(fruits[index]);

    useEffect(() => {
        const interval = setInterval(() => {
            setIndex(index === fruits.length - 1 ? 0 : index + 1)
            setCurrentFruit(fruits[index])
        }, currentFruit.duration);
        return () => clearInterval(interval);
    }, []);

    return (
        <div className="App">
            <header className="App-header">
                {currentFruit.name}: {currentFruit.duration}
            </header>
        </div>
    );
};

export default IntervalExample;

Upvotes: 1

Views: 1354

Answers (3)

Nithish
Nithish

Reputation: 5999

If you want to use different timeouts for each fruit, then setTimeout will be the better approach.

Since setInterval is being called in useEffect the initial value will be set as the interval to change the displayed fruit information.

The problem in your code was while updating the index in the state it's not getting updated properly.

In order to solve that instead of directly updating the value, I'm using updated state value like below

setIndex((index) => (index === fruits.length - 1 ? 0 : index + 1))

const { useState, useEffect } = React;

const fruits = [
  { id: 1, name: "Orange", duration: 2000 },
  { id: 2, name: "Kiwi", duration: 1000 },
  { id: 3, name: "Mango", duration: 3000 }
];

const IntervalExample = () => {
  const [index, setIndex] = useState(0);
  const [currentFruit, setCurrentFruit] = useState(fruits[0]);

  useEffect(() => {
    const interval = setInterval(() => {
      setIndex((index) => (index === fruits.length - 1 ? 0 : index + 1));
    }, fruits[index].duration);
    return () => clearInterval(interval);
  }, []);

  useEffect(() => {
    setCurrentFruit(fruits[index]);
  }, [index]);

  return (
    <div className="App">
      <header className="App-header">
        {currentFruit.name}: {currentFruit.duration}
      </header>
    </div>
  );
};


ReactDOM.render(
       <IntervalExample />,
       document.getElementById("root")
     );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Upvotes: 1

Viet Dinh
Viet Dinh

Reputation: 1951

UseEffect just be called only one time. So, Const interval will init a closure for function call for time interval. It will be never change. Index in closure will be always 0.

Here is my solution, just use one state for index:

const IntervalExample = () => {
    const fruits = [
        {id: 1, name: "Orange", duration: 2000},
        {id: 2, name: "Kiwi", duration: 1000},
        {id: 3, name: "Mango", duration: 3000},
    ]

    const [index, setIndex] = useState(0);

    //because fruit depend on index. Dont need a separate state
    const currentFruit = fruits[index];

    const handleIndex = () => {
        setIndex(index === fruits.length - 1 ? 0 : index + 1)
    }

    useEffect(() => {
        //after render current fruit. We new timeout to set next fruit.
        setTimeout(handleIndex, currentFruit.duration);
    }, [index]);

    return (
        <div className="App">
            <header className="App-header">
                {currentFruit.name}: {currentFruit.duration}
            </header>
        </div>
    );
};

Upvotes: 1

Boussadjra Brahim
Boussadjra Brahim

Reputation: 1

Make your component driven by its states when the index is changed it triggers a useEffect hook that updates the currentFruit state, currentFruit change triggers another useEffect that updates the index and so on, then just use setTimeout like :

const IntervalExample = () => {
    const fruits = [
        {id: 1, name: "Orange", duration: 2000},
        {id: 2, name: "Kiwi", duration: 1000},
        {id: 3, name: "Mango", duration: 3000},
    ]

    const [index, setIndex] = useState(0);
    const [currentFruit, setCurrentFruit] = useState(fruits[index]);

   
    
    useEffect(() => {
        setCurrentFruit(fruits[index])
    }, [index])

    useEffect(() => {
        const interval = setTimeout(() => {
            setIndex(index === fruits.length - 1 ? 0 : index + 1)       
        }, currentFruit.duration);
    }, [currentFruit])

    return (
        <div className="App">
            <header className="App-header">
                {currentFruit.name}: {currentFruit.duration}
            </header>
        </div>
    );
};

Upvotes: 1

Related Questions