5 Essential React Custom Hooks for Everyday Development

5 Essential React Custom Hooks for Everyday Development

I can’t count on one hand how many times I was asked to implement the same thing across different applications. Storing data in LocalStorage, debouncing API calls and so on. This article contains 5 custom React Hooks that I ended up reusing the most.

I’ll save you from the explanation on what are React Hooks and let’s dive in!

1. useDebounce

Debouncing is a technique used to limit how many times a function is called over a period of time. It’s used to improve the performance and UX of an application by reducing the number of times an expensive or time-consuming operation is performed.

Common use cases usually include window resize events, filtering search results as a user types (just look at Amazon’s search bar) or triggering API requests based on user input.

useDebounce.tsx
import { useState, useEffect, useRef } from "react";

const useDebounce = <T>(value: T, delay: number): T => {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
  const timeout = useRef<NodeJS.Timeout>();

  useEffect(() => {
    if (timeout.current) {
      clearTimeout(timeout.current);
    }

    timeout.current = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      clearTimeout(timeout.current);
    };
  }, [value, delay]);

  return debouncedValue;
};

2. useLocalStorage

One of the biggest selling points to use LocalStorage for me is that you can store data on the Client Side. Obviously it won’t replace your server, but it can be the perfect place to store data like preferred theme of the user, login status etc. To sum it up, decreased server load and we can even help apps run better in offline mode or with limited internet connection.

useLocalStorage.tsx
import { useState } from "react";

const useLocalStorage = <T>(
  key: string,
  initialValue: T
): [T, (value: T) => void] => {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);

      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);

      return initialValue;
    }
  });

  const setValue = (value: T) => {
    try {
      setStoredValue(value);

      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.log(error);
    }
  };

  return [storedValue, setValue];
};

3. useClickOutside

This hook is a custom React hook that allows you to detect when a user clicks outside of a specific element. This can be useful in a variety of situations, such as when you want to close a modal or dropdown menu when the user clicks outside of it, or when you want to trigger an action when the user clicks outside of a specific element.

useClickOutside.tsx
import { useState, useEffect, RefObject } from "react";

const useClickOutside = <T extends HTMLElement = HTMLElement>(
  ref: RefObject<T>,
  callback: () => void
): [React.RefObject<T>, boolean] => {
  const [isActive, setIsActive] = useState(false);

  function handleClickOutside(event: MouseEvent) {
    if (ref.current && !ref.current.contains(event.target as Node)) {
      setIsActive(false);
      callback();
    }
  }

  useEffect(() => {
    document.addEventListener("click", handleClickOutside);

    return () => {
      document.removeEventListener("click", handleClickOutside);
    };
  });

  return [ref, isActive];
};

Using it couldn’t be simpler, all it requires is a ref of the element that you want to monitor (anything outside that element will trigger this) and the second argument is a callback.

App.tsx
import { useRef } from "react";

const ReactComponent = () => {
  const customRef = useRef(null);

  useClickOutside(customRef, () => {
    console.log("You clicked outside the element!");
  });

  return (
    <div
      ref={customRef}
      style={{ width: 400, height: 400, background: "red" }}
    ></div>
  );
};

4. useIntersectionObserver

This hook can be used to detect if an element is in the viewport or not. It can be used to implement infinite scroll — what you see on Instagram or TikTok, that you scroll and the content never ends — or to implement scroll based animations to improve the feel of your site. The options are endless.

useIntersectionObserver.tsx
import { useState, useEffect, useRef, RefObject } from "react";

const useIntersectionObserver = <T extends Element>(): [
  RefObject<T>,
  boolean
] => {
  const [isIntersecting, setIsIntersecting] = useState(false);
  const ref = useRef<T>(null);

  useEffect(() => {
    const observer = new IntersectionObserver(([entry]) => {
      setIsIntersecting(entry.isIntersecting);
    });

    if (ref.current) {
      observer.observe(ref.current);
    }

    return () => {
      observer.disconnect();
    };
  }, []);

  return [ref, isIntersecting];
};

5. useOrientationChange

Often times when it comes to responsive design, it’s crucial to make sure that our apps display properly on different screen sizes. But some applications might not look the same or might give a worse UX hence they need to apply additional logic/styling based on the orientation of the screen.

The following hook calculates the window width and height and determines if the browser is in portrait or landscape mode.

useOrientationChange.tsx
import { useState, useEffect } from "react";

type Orientation = "portrait" | "landscape" | null;

const useOrientationChange = (): Orientation => {
  const [orientation, setOrientation] = useState<Orientation>(null);

  useEffect(() => {
    function handleOrientationChange(event: Event) {
      const { innerHeight, innerWidth } = event.target as Window;

      setOrientation(innerWidth > innerHeight ? "landscape" : "portrait");
    }

    window.addEventListener("resize", handleOrientationChange as EventListener);

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

  return orientation;
};

This is all I have for now, let me know if this article was helpful in any shape or form. I have a couple more custom hooks that I use on a daily basis that might help so look out for Part 2!