My Goal:
I'm trying to build a ponent that when you give it props.items
and props.fadeEvery
, it will act as a text rotator. I eventually want it to fade in an out, but I'm having trouble with my window.setInterval
.
Possible Issue:
I'm calling setIndex
in the useEffect
hook, but is that not good practice? How an I have it iterate through the array items infinitely?
TextFade.tsx
// Imports: Dependencies
import React, { useState, useEffect } from 'react';
// TypeScript Type: Props
interface Props {
items: Array<string>,
fadeEvery: number,
};
// Component: Text Fade
const TextFade: React.FC<Props> = (props): JSX.Element => {
// React Hooks: State
const [ index, setIndex ] = useState<number>(0);
// React Hooks: Lifecycle Methods
useEffect(() => {
const timeoutID: number = window.setInterval(() => {
// End Of Array
if (index > props.items.length) {
// Set Data
setIndex(0);
}
else {
// Set Data
setIndex(index + 1);
}
}, props.fadeEvery * 1000);
// Clear Timeout On Component Unmount
return () => window.clearTimeout(timeoutID);
}, []);
return (
<div id="text-fade-container">
<p id="text-fade-text">{props.items[index]}</p>
</div>
);
};
// Exports
export default TextFade;
My Goal:
I'm trying to build a ponent that when you give it props.items
and props.fadeEvery
, it will act as a text rotator. I eventually want it to fade in an out, but I'm having trouble with my window.setInterval
.
Possible Issue:
I'm calling setIndex
in the useEffect
hook, but is that not good practice? How an I have it iterate through the array items infinitely?
TextFade.tsx
// Imports: Dependencies
import React, { useState, useEffect } from 'react';
// TypeScript Type: Props
interface Props {
items: Array<string>,
fadeEvery: number,
};
// Component: Text Fade
const TextFade: React.FC<Props> = (props): JSX.Element => {
// React Hooks: State
const [ index, setIndex ] = useState<number>(0);
// React Hooks: Lifecycle Methods
useEffect(() => {
const timeoutID: number = window.setInterval(() => {
// End Of Array
if (index > props.items.length) {
// Set Data
setIndex(0);
}
else {
// Set Data
setIndex(index + 1);
}
}, props.fadeEvery * 1000);
// Clear Timeout On Component Unmount
return () => window.clearTimeout(timeoutID);
}, []);
return (
<div id="text-fade-container">
<p id="text-fade-text">{props.items[index]}</p>
</div>
);
};
// Exports
export default TextFade;
Share
Improve this question
asked Apr 7, 2021 at 20:49
jefelewisjefelewis
2,0592 gold badges34 silver badges68 bronze badges
3
-
index
is in what's called a closure. Your use effect has been told to only render once[]
.. So you need to use the callback version ofsetIndex
– Keith Commented Apr 7, 2021 at 20:54 -
1
Just another heads up, if your using
setInterval
, it's most likely better toclearInterval
notclearTimeout
. MDN does say the ID's are shared, but for clarity its best to keep them in sync. – Keith Commented Apr 7, 2021 at 21:11 - Does this answer your question? State not updating when using React state hook within setInterval – Yangshun Tay Commented Dec 17, 2022 at 0:42
3 Answers
Reset to default 4Your index values are taken from initital closure and it won't update unless useEffect callback is called again. You can instead use functional way to update state
useEffect(() => {
const timeoutID: number = window.setInterval(() => {
// End Of Array
setIndex(prevIdx => {
if (prevIdx > props.items.length) {
// Set Data
return 0;
}
else {
// Set Data
return prevIdx + 1;
}
})
}, props.fadeEvery * 1000);
// Clear Timeout On Component Unmount
return () => window.clearTimeout(timeoutID);
}, []);
Below I've knocked up a snippet using the callback version of setState, this avoid the closure issue you get by using useEffect with []
..
const {useState, useEffect} = React;
const TextFade = (props) => {
// React Hooks: State
const [ index, setIndex ] = useState(0);
// React Hooks: Lifecycle Methods
useEffect(() => {
const timeoutID: number = window.setInterval(() => {
// End Of Array
setIndex(index =>
index + 1 >= props.items.length
? 0
: index + 1);
}, props.fadeEvery * 1000);
// Clear Timeout On Component Unmount
return () => window.clearInterval(timeoutID);
}, []);
return (
<div id="text-fade-container">
<p id="text-fade-text">{props.items[index]}</p>
</div>
);
};
ReactDOM.render(<TextFade items={['one','two', 'three']} fadeEvery={1}/>, document.querySelector('#mount'));
<script crossorigin src="https://unpkg./react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg./react-dom@17/umd/react-dom.development.js"></script>
<div id="mount"></div>
As @Keith said:
index is in what's called a closure. Your use effect has been told to only render once [].. So you need to use the callback version of setIndex
So, your useEffect
hook will be:
useEffect(() => {
const timeoutID: number = window.setInterval(() => {
// End Of Array
if (index > props.items.length) {
// Set Data
setIndex(0);
} else {
// Set Data
setIndex(index + 1);
}
}, props.fadeEvery * 1000);
// Clear Timeout On Component Unmount
return () => window.clearTimeout(timeoutID);
}, [index]);
Here is the working demo at CodeSandbox.