Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import { useState, useEffect } from 'react';
// Usage
function App() {
const size = useWindowSize();
return (
<div>
{size.width}px / {size.height}px
</div>
);
}
// Hook
function useWindowSize() {
// Initialize state with undefined width/height so server and client renders match
// Learn more here: https://joshwcomeau.com/react/the-perils-of-rehydration/
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
// Handler to call on window resize
function handleResize() {
// Set window width/height to state
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
// Add event listener
window.addEventListener("resize", handleResize);
// Call handler right away so state gets updated with initial window size
handleResize();
// Remove event listener on cleanup
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount
return windowSize;
}
@lax4mike
Copy link

lax4mike commented Jul 7, 2020

Hello! 👋🏻

This code has an issue with server rendering. I ran in to a similar issue here: gatsbyjs/gatsby#14601

The underlying issue is in getSize

return {
  width: isClient ? window.innerWidth : undefined,
  height: isClient ? window.innerHeight : undefined
};

The problem is that the server render value of windowSize and the first client render won't match. (eg, width on the server will be undefined, client will be window.innerWidth). This confuses React and you will see incorrect values on first load, as described in the github issue above and demonstrated here on a large screen: http://mikelambert.me/gatsby-bug/

To fix this, I modified the hook to always initialize the values to undefined (so the server render and first client render match) and to update the values after the first render on the client.

export default function useWindowSize() {
  // initialize to undefined so the server render and first client render match
  // https://github.com/gatsbyjs/gatsby/issues/14601 
  const [windowSize, setWindowSize] = useState({
    width: undefined,
    height: undefined,
  });

  useEffect(() => {
    function handleResize() {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }

    window.addEventListener("resize", handleResize);
    handleResize();

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

  return windowSize;
}

@gragland
Copy link
Author

gragland commented Jul 30, 2020

@lax4mike thanks for pointing out the SSR issue! I've updated this gist and the post.

@ivanjeremic
Copy link

ivanjeremic commented Dec 5, 2021

change the useEffect to useLayoutEffect because you see a flicker before it's set in some cases. useLayoutEffect is definitely the way to go here. Here an example where a div is centered based on the body, if you change it to a normal useEffect you will see it flicker. https://codesandbox.io/s/still-wave-q0pk7

@mimiqkz
Copy link

mimiqkz commented Jan 21, 2022

Long time has passed , why haven't this been updated with debounce or throttle ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment