1

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;

3 Answers 3

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>
    );
};

Sign up to request clarification or add additional context in comments.

2 Comments

Shouldn't you cleanup your intervals in the useEffect?
it's just a setTimeout which doesn't need a clear
1

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>
    );
};

Comments

1

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.