CreaTian
CreaTian

Reputation: 1

React hook's state using scroll events issue

This is a first time, when I use react hooks. I try to disable fetching, using a state (boolean). But there is a problem, because a function still see's initial state and execute function dozen times instead once. I can't see the point. In the react console the state is marked as true, and still pass my if statement

I noticed fact, that without listener on window it is working. So I tried to use event build in React - onScroll, but thats not resolve my issue - nothing happened. My version of React: 16.8.6

const [isFetchedData, setIsFetchedData] = useState(false);
 const fetchBookData = async () => {
    await setisFetchedData(true);
    ... - fetchedData
    setTimeout(() => {
      setIsFetchedData(false);
    }, 5000); --- I would like to make available to fetch data again after 5 seconds.
  };

  useEffect(() => {
    window.addEventListener("scroll", scrollHandler);
  });

  const scrollHandler = () => {
    if (!isFetchedData) {
      fetchBookData(); --- always executing
    }
  };

Fleischpflanzerl - I put that function in body of useEffect. The function scrollHandler in not executing now. Can you give me some of example how to fix that?

useEffect(() => {
    return () => {
      window.addEventListener("scroll", scrollHandler, true);
      const scrollHandler = () => {
        if (!isFetchedData) {
          // fetchBookData();
          console.log("fetch");
        }
      };
    };
  }, [isFetchedData]);

Upvotes: 0

Views: 4868

Answers (1)

wiktor
wiktor

Reputation: 1637

I think you should use useRef instead of useState in this situation.

What's wrong when you call useState?

If you call setIsFetchedData to set isFetchedData, you trigger a re-render in your component. So every time you scroll, you trigger a component re-render.

The setState function is used to update the state. It accepts a new state value and enqueues a re-render of the component.

The main problem is that isFetchedData is just a boolean in your hook. Not a magical variable which instantly change it's value. (React only enqueues the re-render, it won't re-render immediately.)

But when scroll events occur multiple times the event handler function will see isFetchedData as a static false. And if it is false you call fetch once again.

The point is isFetchedData won't change until the next re-render calls useState once again.

Another problem in your code, you forgot to define a clean-up function and dependencies. In every render, React calls useEffect so if you don't return a clean-up function and don't define the dependencies which tell React when to run this effect, you call the addEventListener every time the component re-renders and attach the handler multiple times.

I made a working example with useRef, maybe is worth more than thousand words.

const useRef = React.useRef;
const useEffect = React.useEffect;

const useScrollFetch = () => {
	const isFetchedData = useRef(false);

	function fakeFetchBookData() {
		console.log("fakeFetchBookData start...");
		setTimeout(() => {
		  console.log("fakeFetchBookData end...");
    }, 2000);
	}
	function fetchBookData() {
		isFetchedData.current = true;
		fakeFetchBookData();
		setTimeout(() => {
			console.log("isFetchedData reset");
			isFetchedData.current = false;
		}, 5000); // I would like to make available to fetch data again after 5 seconds.
	}

	const scrollHandler = event => {
		if (!isFetchedData.current) {
			console.log(
				"scrollHandler: ",
				event.type,
				"isFetchedData: ",
				isFetchedData
			);
			fetchBookData();
		}
	};
	useEffect(() => {
		window.addEventListener("scroll", scrollHandler, true);
		return () => {
			window.removeEventListener("scroll", scrollHandler, true);
		};
	}, []);
};

function App() {
  console.log("App render...");
	useScrollFetch();
	return (
		<div className="App" style={{ width: '10%' }}>
			<p>
				Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed
				dictum, enim vitae porttitor vulputate, sem nulla faucibus
				felis, a faucibus ex lacus at mauris. Mauris scelerisque rhoncus
				nisi vel commodo. Suspendisse varius lectus porta lectus
				commodo, consectetur condimentum nibh euismod. Aenean cursus
				nibh erat, non rhoncus arcu porta vel. Pellentesque maximus
				mauris nec neque porttitor commodo. In condimentum hendrerit
				augue, vitae lobortis est iaculis quis. Fusce vehicula sit amet
				dui vel dignissim. Donec tempus hendrerit tristique. Nam
				suscipit aliquet nisl eu malesuada.
			</p>
		</div>
	);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>

<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>

(I had to remove async/await because I can't embed the code here with these syntax, but this isn't an issue.)

I really suggest to read A Complete Guide to useEffect from Dan Abramov. It helped me a lot to better understand how useEffect works. :)

Upvotes: 2

Related Questions