I have a function handleScroll
that is listened on the scroll event. Thsi function must update isFetching
(that starts false and must change the boolean value).
The function handleScroll
is correctly listened, as the console.log
shows. However, isFetching
is always false.
It seems like setIsFetching
is never read. Another option, I think, is like the eventListener freezes the first version of the handleScroll function.
How can I do in order to update the hook in that function? Here is a simplified version of the code and the codesandbox :
/* <div id='root'></div> */
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const debounce = (func, wait, immediate) => {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};
const App = () => {
const [isFetching, setIsFetching] = useState(false);
const handleScroll = debounce(() => {
setIsFetching(!isFetching);
console.log({ isFetching });
}, 300);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return <div style={{ height: "1280px" }}>Hello world</div>;
};
const root = document.getElementById("root");
if (root) ReactDOM.render(<App />, root);
UPDATE
I put an empty array as a second param in the useEffect
because I want that the first param function only fires once on componentDidMount()
Add isFetching
as a dependency to useEffect
While i can't provide a deep explanation, I can say that you basically lied to React in useEffect
when you said the effect doesn't depend on anything by providing an empty array of dependencies it's always good to pass all the variables that include in your effect.
Also you create a new function every time the component re-render
, to avoid this move the function inside of useEffect
or wrap it inside useCallback
which will not create re-create the function unless something in the array of dependencies changes
useEffect(
() => {
const handleScroll = debounce(() => {
setIsFetching(prevState => !prevState);
console.log({ isFetching });
}, 300);
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
},
[isFetching]
);
Or with useCallback
const handleScroll = useCallback(
debounce(() => {
setIsFetching(prevState => !prevState);
console.log({ isFetching });
}, 300),
[isFetching]
);
useEffect(
() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
},
[isFetching]
);
complete guide to useEffect
In order to listen the changes in your state from inside the useEffect
callback (when you're not following any props update), you can save your state in a variable outside your component's scope, and using it instead of the state directly.
Here you have the code:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const debounce = (func, wait, immediate) => {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
timeout = null;
if (!immediate) func.apply(context, args);
}, wait);
if (immediate && !timeout) func.apply(context, args);
};
};
let isFetchingState;
const App = () => {
const [isFetching, setIsFetching] = useState(false);
isFetchingState = isFetching;
const handleScroll = debounce(() => {
setIsFetching(!isFetchingState);
console.log({ isFetchingState });
}, 300);
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return <div style={{ height: "1280px" }}>Hello world</div>;
};
const root = document.getElementById("root");
if (root) ReactDOM.render(<App />, root);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With