🪝 5 Custom React Hooks For Your Next Project

🪝 5 Custom React Hooks For Your Next Project

Cover Photo by Maggie Appleton

If you’re a regular user of React its hard to miss the functionality of hooks. Hooks allow us to easily reuse state between different components without prop drilling. Hooks can also be used to unwrap the value of a promise to access data from an api or database.

Key Points

  • React hooks may only be called inside a React function
  • Only call your hooks at the top level of the React function, not inside nested functions or loops.
  • Preface your custom hooks with “use”, ie “useAuth” or “useAlert”.

Here are 5 utility based hooks written in Typescript that I use in every project I work on.

1. 💽 useLocalStorage

A browser’s local storage is a valuable place to persist data. Some applications are only used on the client and local storage can be a succinct way to save data in between sessions for reuse by the user.

import { useState, useEffect } from "react";

export default function useLocalStorage(key: string, firstValue: any = null) {
  const initialValue = localStorage.getItem(key) || firstValue;
  const [item, setitem] = useState(initialValue);

  useEffect(
    function SetKeyInLocalStorage() {

      if (item === null) {
        localStorage.removeItem(key);
      } else {
        localStorage.setItem(key, item);
      }
    },
    [key, item]
  );

  return [item, setitem];
};

2. 🐭 useMousePosition

Having access to the mouse position is useful when needing to render a component differently based on its position on the screen. For instance, you may have an element that shows a dropdown below it on hover. If the element is near the bottom of the screen at the time of hovering the dropdown may render off screen. In this case the mouse position can be used to conditionally render the dropdown above the element if the mouse position is near the bottom of the screen.

import { useState, useEffect } from "react";

export default function useMousePosition() {
  const [x, setX] = useState<number>(0);
  const [y, setY] = useState<number>(0);

  useEffect(() => {
    const updateMouse = () => {
      window.addEventListener("mousemove", (e) => {
        setX(e.clientX);
        setY(e.clientY);
      });
    };

    window.addEventListener("mousemove", updateMouse);

    return () => {
      window.removeEventListener("mousemove", updateMouse);
    };
  }, []);

  return { x, y };
};

3. 🖥️ useViewport

Most apps today are used across a multitude of different sized devices. Its imperative that developers are building applications that are responsive to these changes and render a component appropriately for its current window size.. This ability begins with subscribing to changes in both the width and height of the device being used.

import { useState, useEffect } from "react";

export default function useViewport() {
  const [width, setWidth] = useState<number>(window.innerWidth);
  const [height, setHeight] = useState<number>(window.innerHeight);

  useEffect(() => {
    const handleResize = () => {
      setWidth(window.innerWidth);
      setHeight(window.innerHeight);
    };

    window.addEventListener('resize', handleResize);

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

  return { width, height };
};

4. 🚸 usePath

Sometimes knowledge of what path or query string is being used is needed to making a rendering decision.

import { useState, useEffect } from "react";

export default function usePath() {
  const [path, setPath] = useState<string>(window.location.pathname);

useEffect(() => {
    const listenToPopState = () => {
  const windowPath = window.location.pathname;
    setPath(windowPath);
};

  window.addEventListener("popstate", listenToPopState);

  return () => {
    window.removeEventListener("popstate", listenToPopState);
  };
}, []);

return path;
};

5. 👂 useEventListener

You may have noticed that some of the above hooks have repetitive code inside them. Since custom hooks can call other custom hooks from inside them we can mitigate this by creating a custom hook for adding an event listener and then calling it inside your other custom hooks.

import { useRef, useEffect, useCallback, RefObject } from 'react';

interface UseEventListenerProps {
  type: keyof WindowEventMap;
  listener: EventListener;
  element?: RefObject<Element> | Document | Window | null;
}

export const useEventListener = ({
  type,
  listener,
  element =  window,
}: UseEventListenerProps): void => {
  const savedListener = useRef<EventListener>();

  useEffect(() => {
    savedListener.current = listener;
  }, [listener]);

  const handleEventListener = useCallback((event: Event) => {
    savedListener.current?.(event);
  }, []);

  useEffect(() => {
    const target = element && 'current' in element ? element.current : element;
    target?.addEventListener(type, handleEventListener);
    return () => target?.removeEventListener(type, handleEventListener);
  }, [type, element, handleEventListener]);
};

To learn more about React Hooks check out the official 📄 documentation.

Feel free to use these custom hooks in your next project if you aren’t already!