loading...
Cover image for How to Create a useWindowSize() React Hook

How to Create a useWindowSize() React Hook

codeartistryio profile image Reed Barger Originally published at reedbarger.com on ・4 min read

Within my Gatsby site, I have a header, and as I decrease the size of the page, I want to show less links.

resizing window to show header

To do this we could use a media query (CSS), or we could use a custom react hook to give us the current size of the page and hide or show the links in our JSX.

Previously, I was using a hook from the a library called react-use. Instead of bringing an entire third-party library, I decided to create my own hook that would that provide the dimensions of the window, both the width and height. I called this hook useWindowSize.

Creating the hook

First, we’ll create a new file .js in our utilities (utils) folder, the same name as the hook useWindowSize and I’ll import React (to use hooks) while exporting the custom hook.

// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {}

Now since I’m using this within a Gatsby site, which is server rendered, I need to get the size of the window, but we may not have access to it because we’re on the server. To check and make sure we’re not on the server, we can see if type of window is not equal to the string undefined.

In which case we can return to a default width and height for a browser, say, 1200 and 800 within an object:

// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  if (typeof window !== "undefined") {
    return { width: 1200, height: 800 };
  }
}

Getting the width and height from window

And assuming we are on the client and can get the window, we can take the useEffect hook to perform a side effect by interacting with window. We’ll include an empty dependencies array to make sure the effect function is called only once the component (that this hook is called in) is mounted.

To find out the window width and height, we can add an event listener and listen for the resize event. And whenever the browser sizes change, we can update a piece of state (created with useState), which we’ll call windowSize and the setter to update it will be setWindowSize.

// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  if (typeof window !== "undefined") {
    return { width: 1200, height: 800 };
  }

  const [windowSize, setWindowSize] = React.useState();

  React.useEffect(() => {
    window.addEventListener("resize", () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    });
  }, []);
}

When the window is resized, the callback will be called and the windowSize state will be updated with the current window dimensions. To get that, we set the width to window.innerWidth, and height, window.innerHeight.

Adding SSR support

However, the code as we have it here will not work. And the reason is because a key rule of hooks is that they cannot be called conditionally. As a result, we cannot have a conditional above our useState or useEffect hook, before they are called.

So to fix this, we’ll set the initial value of useState conditionally. We’ll create a variable called isSSR, which will perform the same check to see if the window is not equal to the string undefined.

And we’ll use a ternary to set the width and height by first checking to see if we’re on the server. If we are we’ll use the default value and if not, we’ll use window.innerWidth and window.innerHeight.

// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  // if (typeof window !== "undefined") {
  // return { width: 1200, height: 800 };
  // }
  const isSSR = typeof window !== "undefined";
  const [windowSize, setWindowSize] = React.useState({
    width: isSSR ? 1200 : window.innerWidth,
    height: isSSR ? 800 : window.innerHeight,
  });

  React.useEffect(() => {
    window.addEventListener("resize", () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    });
  }, []);
}

Then finally, we need to think about when our components unmount. What do we need to do? We need to remove our resize listener.

Removing resize event listener

You can do that by returning a function from useEffectand we will remove the listener with window.removeEventListener.

// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  // if (typeof window !== "undefined") {
  // return { width: 1200, height: 800 };
  // }
  const isSSR = typeof window !== "undefined";
  const [windowSize, setWindowSize] = React.useState({
    width: isSSR ? 1200 : window.innerWidth,
    height: isSSR ? 800 : window.innerHeight,
  });

  React.useEffect(() => {
    window.addEventListener("resize", () => {
      setWindowSize({ width: window.innerWidth, height: window.innerHeight });
    });

    return () => {
      window.removeEventListener("resize", () => {
        setWindowSize({ width: window.innerWidth, height: window.innerHeight });
      });
    };
  }, []);
}

But since we need a reference to the same function, not two different ones as we have here. To do that, we’ll create a shared callback function to both of the listeners called changeWindowSize.

And finally, at the end of the hook, we will return our windowSize state. And that’s it.

// utils/useWindowSize.js

import React from "react";

export default function useWindowSize() {
  const isSSR = typeof window !== "undefined";
  const [windowSize, setWindowSize] = React.useState({
    width: isSSR ? 1200 : window.innerWidth,
    height: isSSR ? 800 : window.innerHeight,
  });

  function changeWindowSize() {
    setWindowSize({ width: window.innerWidth, height: window.innerHeight });
  }

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

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

  return windowSize;
}

Usage

To use the hook, we just need to import it where we need, call it, and use the width wherever we want to hide or show certain elements.

In my case, this is at the 500px mark. There, I want to hide all of the other links and only show the Join Now button, like you see in the example above:

// components/StickyHeader.js

import React from "react";
import useWindowSize from "../utils/useWindowSize";

function StickyHeader() {
  const { width } = useWindowSize();

  return (
    <div>
      {/* visible only when window greater than 500px */}
      {width > 500 && (
        <>
          <div onClick={onTestimonialsClick} role="button">
            <span>Testimonials</span>
          </div>
          <div onClick={onPriceClick} role="button">
            <span>Price</span>
          </div>
          <div>
            <span onClick={onQuestionClick} role="button">
              Question?
            </span>
          </div>
        </>
      )}
      {/* visible at any window size */}
      <div>
        <span className="primary-button" onClick={onPriceClick} role="button">
          Join Now
        </span>
      </div>
    </div>
  );
}

This hook will work on any server rendered React app, such as Gatsby and Next.js.

Want To Become a JS Master? Join the 2020 JS Bootcamp

Join the JS Bootcamp Course

Follow + Say Hi! 🎨 TwitterInstagramreedbarger.comcodeartistry.io

Posted on by:

codeartistryio profile

Reed Barger

@codeartistryio

Sharing artful coding skills that fuel the life you want to live @ CodeArtistry.io 🎨

Discussion

markdown guide