Learn Custom Hooks in 10 Minutes
Learn Custom Hooks in 10 Minutes

In this post I want to show you how to build 3 custom and reusable hooks inside React.

useLocalStorage hooks

The first hook that we will implement is useLocalStorage hooks. Why do we need it? We want to work with localStorage in React hooks way. And this is really easy to do.

Here I have a component CookieBanner which will use our useLocalStorage hooks.

import useLocalStorage from "./hooks/useLocalStorage";

const CookieBanner = () => {
  const [showCookiesBanner, setCookiesBanner] = useLocalStorage(
    "cookiesBanner"
  );
  return (
    <div>
      <button onClick={() => setCookiesBanner("closed")}>Close banner</button>
      {!showCookiesBanner && <div>cookie banner</div>}
    </div>
  );
};

export default CookieBanner;

This is just a plain component which uses our future useLocalStorage hook. Here you can see it's usage. As you can see we call useLocalStorage and we provide inside a name of the key inside localStorage where we want to save this property. Back we get a value and a setter to change it. So it is working exactly like for example useState hook. And the idea is that we have in sync our local storage property and the same property inside memory.

Also as you can see we have a button which calls our setCookiesBanner setter to change the value of our localStorage key. We also show our cookie banner only when our property showCookiesBanner is there.

Now let's try to implement this hook together.

// hooks/useLocalStorage.js
import { useState, useEffect } from "react";

const useLocalStorage = (key, initialValue = "") => {
  const [value, setValue] = useState(() => {
    return localStorage.getItem(key) || initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, value);
  }, [value, key]);

  return [value, setValue];
};

export default useLocalStorage;
  • So our useLocalStorage hook is just a function which gets key and initialValue if provided
  • Inside we create a state for our key and we try to get a value from localStorage on initialize
  • We simply return our value and setter as a normal useState hook.
  • We added useEffect because every time when we change our state we need to set a value to localStorage

As you can see in browser cookie banner is there by default and after we click on our button it disappears. Also you can see that in localStorage our value was changed to closed which means everything is working correctly.

Now with just a single line we can use localStorage logic anywhere in our application.

UseOnline hook

The second hook that I want to show you will check if user is online on not. Here is our component with implementation example.

import useOnline from "./hooks/useOnline";

const CookieBanner = () => {
  const isOnline = useOnline();
  console.log("isOnline", isOnline);
  return (
    <div>
      {isOnline && <div>I'm online</div>}
      {!isOnline && <div>I'm offline</div>}
    </div>
  );
};

export default CookieBanner;

As you can see there are no parameters inside useOnline and we simply get back true or false. Now let's try to implement it.

// hooks/useOnline.js
import { useState, useEffect } from "react";

const getOnline = () => navigator.onLine || true;

const useOnline = () => {
  const [onlineStatus, setOnlineStatus] = useState(getOnline());

  useEffect(() => {
    window.addEventListener("online", () => setOnlineStatus(true));
    window.addEventListener("offline", () => setOnlineStatus(false));
  }, []);

  return onlineStatus;
};

export default useOnline;
  • We created an additional function getOnline to return navigator.onLine or true if it is older browser
  • We created a state where we put our online status on initialize
  • We added useEffect with 2 window listeners for online and offline where we change our state

As you can see in browser we get I'm online message which means our custom hook is working correctly.

UseClickOutside hook

The last hook that I want to show you is click outside hook. It is needed for every application with modals or tooltip which we can close by clicking outside of the element.

import { useRef, useState } from "react";
import useClickOutside from "./hooks/useClickOutside";

const ClickOutside = () => {
  const [showTooltip, setShowTooltip] = useState(false);
  const clickRef = useRef();
  useClickOutside(clickRef, () => {
    setShowTooltip(false);
  });
  return (
    <div ref={clickRef}>
      <button onClick={() => setShowTooltip(!showTooltip)}>
        Toggle tooltip
      </button>
      {showTooltip && (
        <div
          style={{
            border: "2px dashed orangered",
            height: 100,
            width: 200,
            display: "flex",
            justifyContent: "center",
            alignItems: "center",
          }}
        >
          Tooltip
        </div>
      )}
    </div>
  );
};

export default ClickOutside;

Here is a component with tooltip which we can toggle by changing local state. Most importantly we create a clickRef here which gives us access to DOM node of our element. Also we can useClickOutside hook and provide inside our DOM node and a callback which will happen when we click outside of the element.

Now let's implement our hook.

import { useEffect } from "react";

const useClickOutside = (ref, callback) => {
  const handleClick = (e) => {
    if (ref.current && !ref.current.contains(e.target)) {
      callback();
    }
  };
  useEffect(() => {
    document.addEventListener("click", handleClick);
    return () => {
      document.removeEventListener("click", handleClick);
    };
  });
};

export default useClickOutside;
  • We get here ref which is DOM node and a callback
  • We created useEffect to add a click listener on the whole document
  • We call handleClick which checks if we clicked outside of our ref on not and we call callback when we clicked outside
  • Super important is to remove a document listener in useEffect as we will attach document listeners with every use of this hook which is not performant

As you can see in browser now our tooltip dissapears when we click outside.

Conclusion

So we succesfully built 3 custom hooks which we can reuse in every single project. Also if you are interested in 5 coding tasks that you can see in almost every Javascript interview make sure to check this post also.

📚 Source code of what we've done