Implementing and Understanding the `useWindowSize` React Custom Hook

5 min read

📚 Quick Review: This practical application is built upon a fundamental programming concept. Review the Theory Lesson here first.


Dissecting `useWindowSize`: A Line-by-Line Practical Guide

Understanding the theory behind React’s Custom Hooks is one thing; implementing and truly grasping their mechanics is another. In this practical lesson, we’ll break down the useWindowSize custom hook, examining each line of code to understand its purpose, how it leverages React’s core hooks, and how it provides a robust solution for tracking browser window dimensions.

The `useWindowSize` Hook: Code Breakdown

Let’s start with the complete code snippet for our useWindowSize hook:

import { useState, useEffect } from 'react';
function useWindowSize() {
  const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight });
  useEffect(() => {
    function handleResize() { setWindowSize({ width: window.innerWidth, height: window.innerHeight }); }
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  return windowSize;
}

Line-by-Line Explanation:

import { useState, useEffect } from 'react';

This line imports two fundamental React Hooks: useState and useEffect. useState allows functional components to manage local state, while useEffect enables them to perform side effects (like data fetching, subscriptions, or manually changing the DOM) after render.

function useWindowSize() {

This defines our custom hook. By convention, all custom hooks in React start with the prefix “use“. This naming convention signals to React’s linter and other developers that this function adheres to the Rules of Hooks (e.g., calling hooks only at the top level of a React function or custom hook, and not from regular JavaScript functions).

const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight });

Here, we initialize a piece of state using useState. windowSize will hold an object containing the current width and height of the browser window. setWindowSize is the function we’ll use to update this state. The initial state is set to the current window.innerWidth and window.innerHeight at the time the hook is first called. This ensures that the hook provides accurate dimensions immediately upon component mount.

useEffect(() => {

This is where the side effect logic resides. useEffect runs after every render by default, but its behavior can be controlled by its dependency array. The arrow function passed to useEffect is the effect itself.

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

This nested function, handleResize, is our event handler. Whenever the browser’s 'resize' event fires, this function will be executed. It updates the windowSize state with the new window.innerWidth and window.innerHeight. Updating the state will trigger a re-render of any component that uses this hook, providing them with the latest dimensions.

window.addEventListener('resize', handleResize);

Inside the useEffect, we attach our handleResize function as a listener to the global window object’s 'resize' event. This means that whenever the browser window is resized, our handleResize function will be invoked.

return () => window.removeEventListener('resize', handleResize);

This is a critical part of useEffect for managing side effects that involve subscriptions or event listeners. The function returned by useEffect is a cleanup function. React will execute this function when the component unmounts, or before the effect re-runs (if dependencies change). In this case, it removes the 'resize' event listener, preventing memory leaks and ensuring that the listener doesn’t persist in memory unnecessarily.

}, []);

The empty dependency array [] is crucial here. It tells React that this effect should only run once after the initial render and that the cleanup function should only run once when the component unmounts. If this array were omitted, the effect would re-run after every render, re-attaching and re-detaching the event listener unnecessarily. If it contained dependencies, the effect would re-run whenever those dependencies changed. An empty array ensures the listener is set up once and cleaned up once.

return windowSize;

Finally, the useWindowSize hook returns the current windowSize state object. This allows any component consuming the hook to easily access the width and height values.

How to Use `useWindowSize` in a Component

Using the hook is straightforward:

import React from 'react';
import useWindowSize from './useWindowSize'; // Assuming the hook is in useWindowSize.js

function MyResponsiveComponent() {
  const { width, height } = useWindowSize();

  return (
    

Window Dimensions

Width: {width}px

Height: {height}px

{width < 768 ? (

This content is visible on small screens!

) : (

This content is visible on large screens!

)}
); } export default MyResponsiveComponent;

In this example, MyResponsiveComponent simply calls useWindowSize() and destructures the returned width and height. It then uses these values to display the dimensions and conditionally render content, demonstrating its utility for responsive design.

Execution Environment and React’s Lifecycle

The useWindowSize hook operates within the standard React component lifecycle and the browser’s JavaScript environment:

  • Initial Render: When a component using useWindowSize mounts, useState initializes windowSize with current dimensions. The useEffect callback runs, attaching the 'resize' event listener.
  • Window Resize: When the user reizes the browser window, the 'resize' event fires. The handleResize function updates the windowSize state.
  • Re-render: React detects the state change in windowSize and re-renders the component(s) that use the hook, providing them with the new dimensions.
  • Unmount: When the component unmounts, the cleanup function returned by useEffect executes, removing the event listener and preventing resource leaks.
💡 Developer Tip: While the empty dependency array [] in useEffect ensures the listener is added and removed only once, the handleResize function itself is recreated on every render if it’s defined inside the effect. For performance-critical applications, or if handleResize had external dependencies, you might wrap handleResize with useCallback to memoize it. However, in this simple case, where handleResize only depends on setWindowSize (which is stable), it’s generally not necessary. The key is the cleanup function preventing multiple listeners.

Leave a Reply

Your email address will not be published. Required fields are marked *