I am making a NewsCardComponent which will display slideshow of images provided in an array which will be passed down as a prop. Everytime the component is used, will have a different number of elements in images array. so i put the "imgArr" in src of img as:
<img src={imgArr[index]}>
where "index" is the state and i have to dynamically check if a component has reached to the end of array then setIndex to zero. I have achieved what I wanted but i dont know why all the techniques other than first are not working.
My useEffect:
useEffect(() => {
const interval = setInterval(() => {
indexResetter();
}, 2000);
return () => {
clearInterval(interval);
};
}, []);
Technique 1 (working fine) :
function indexResetter() {
setIndex((prev) => {
let newIndex = prev 1;
if (newIndex > imgArr.length - 1) {
newIndex = 0;
}
return newIndex; }); }
Technique 2 (state is not setting to zero but increasing infinitely):
function indexResetter() {
let newIndex = index 1;
if (newIndex === imgArr.length - 1) {
setIndex(0);
} else {
setIndex((prev) => prev 1);
}
}
Technique 3 (same problem with second one):
function indexResetter() {
if (index >= imgArr.length - 1) {
setIndex(0);
} else {
setIndex((prev) => prev 1);
}
}
CodePudding user response:
From what I am reading, your use effect only runs during the first render. Put index in the dependency array in your useEffect so that it runs everytime that your index changes
CodePudding user response:
In short, your useEffect()
runs on initial mount, meaning that the setInterval()
continues to execute the indexResetter
function that was defined on the initial render, even after subsequent rerenders. That means the version of the indexResetter
function that you end up executing only knows about the index
state upon the initial mount, giving you the issue where your index
doesn't change.
For more details, when you define a function, it stores a reference to the "scope" it's defined in, which includes all the variables defined in that scope:
function NewsCardComponent() {
const [index, setIndex] = useState(0);
function indexResetter() {
...
}
}
When your component renders, it does the following:
- NewsCardComponent() gets called, creating a new "scope" (formally an environment record). This scope holds the variables such as
index
- The
indexResetter
function gets created (it's not being called yet). This function stores an internal reference to the scope created at step 1. This is called a closure.
Later on, when indexResetter()
gets called, it uses the scope that it stored internally at step 2 to work out where to find look for index
if it can't find it within the indexResseter
function itself.
When you update your index
state using setIndex()
, your component rerenders, and performs the above two steps 1 and 2 again. Creating a new scope for the NewsCardComponent
that now holds the updated value of index, as well as creating a new indexResetter
function. This means that each time you call setIndex
, you effectively create new versions of indexResster
function that can "see" the new vale of index
.
Your problem is that your useEffect()
only runs on the initial mount of your function, so the function that you're calling within your setInterval()
is the initial indexResetter
function that only has visibility of index
state for when your component initially mounted:
const interval = setInterval(() => {
indexResetter();
}, 2000);
On subsequent rerenders, the indexResetter
function will be redeclared, but the above setInterval()
will continue to call the version of the indexResster
function that was defined on the initial render, which only knows about the index
state at that time. As a result, the value of index
within the function ends up always being the initial value.
In your working example, you're using the state setter function to access prev
, which means you're no longer relying on the index
value from the surrounding scope. The state setter function will provide you with the most up to date value of your state, and so you don't face the same issue.