1

I want to observer when window stop resizing, cause currently if i listen to the resize event on window, i could only listen to resizing change but not knowing when resizing stops. I checked the event parameter in resizing callback, but did not find and helpful information.

Below is the code i'm trying to complete:

import React, { useState, useEffect } from "react";

export default function App() {
  const [windowResizing, setWindowResizing] = useState(false);

  const handleWindowResize = e => {
    setWindowResizing(true);
  };

  useEffect(() => {
    window.addEventListener("resize", handleWindowResize);

    return () => window.removeEventListener("resize", handleWindowResize);
  }, []);

  return <div>{JSON.stringify({ windowResizing })}</div>;
}

But this does not work, since windowResizing will keep being true after resizing begins, even i already stops resizing.

So is there any way to observe when window stop resizing and i can then call setState based on that?

Limboer
  • 373
  • 4
  • 24

2 Answers2

2

There is no real "resizing state" in the browser; the resize event occurs and then it's over.

You could emulate a resizing state of your own by setting a state variable to true on every resize event, and start a new timeout that will reset it back to false after a small amount of time.

Example

import React, { useState, useEffect } from "react";

export default function App() {
  const [windowResizing, setWindowResizing] = useState(false);

  useEffect(() => {
    let timeout;
    const handleResize = () => {
      clearTimeout(timeout);

      setWindowResizing(true);

      timeout = setTimeout(() => {
        setWindowResizing(false);
      }, 200);
    }
    window.addEventListener("resize", handleResize);

    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return <div>{JSON.stringify({ windowResizing })}</div>;
}
Tholle
  • 108,070
  • 19
  • 198
  • 189
  • 1
    Cheers, this is exactly what i want, however i got one small question: what's the difference between making the `timeout` as a ref and just putting it as a local variable in `useEffect`? – Limboer Dec 22 '20 at 04:03
  • 1
    @Limboer Great! It's just a matter of preference really. Since the timeout variable was only needed in the effect I put it there as a regular scoped variable. If you would have liked to be able to clear the timeout in e.g. an event handler as well, a ref would have been more appropriate. – Tholle Dec 22 '20 at 04:06
  • Thanks for your detailed explanation. Last question: I see you put `handleResize()` inside `useEffect`, is there any particular reason to do that? Or is it just a matter of coding preference as well? I usually prefer to put function or method independently along inside component so i can reference it in other places. – Limboer Dec 22 '20 at 06:50
  • 1
    @Limboer That's mainly preference as well. Since we pass an empty array as second argument to the effect, it will only be run once after the initial render. By putting `handleResize` in the effect it's clear the same function will be used for `addEventListener` and `removeEventListener`. The same function would be used even if we put it outside of the effect, but it's not as obvious. [This is discussed briefly in the FAQs in the documentation](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies), and that's why I put functions in the effect. – Tholle Dec 22 '20 at 07:04
0

Small update to Tholle's answer

to include the size and the resizing state

import { useState, useLayoutEffect } from 'react'

// https://stackoverflow.com/a/63010184
function debounce(fn, ms) {
    let timer;
    return _ => {
        clearTimeout(timer);
        timer = setTimeout(_ => {
            timer = null;
            fn.apply(this, arguments);
        }, ms);
    };
}

const useWindowSize = () => {
    const [size, setSize] = useState([0, 0, false]);
    useLayoutEffect(() => {
        let timeout;
        clearTimeout(timeout);
        function updateSize() {
            clearTimeout(timeout);
            setSize([window.innerWidth, window.innerHeight, true]);
            timeout = setTimeout(() => {
                setSize([window.innerWidth, window.innerHeight, false]);
            }, 800);
        }
        const debouncedResizeHandler = debounce(() => updateSize())
        window.addEventListener('resize', debouncedResizeHandler);
        setSize([window.innerWidth, window.innerHeight, false]);
        return () => window.removeEventListener('resize', debouncedResizeHandler);
    }, []);
    return size;
}

export default useWindowSize