Reputation: 3128
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
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
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
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