How to write custom REACT HOOKS in 5 minutes

In this post, we are going to see how to create our own React Hook in just a few minutes. If you don't know what is a React Hook, I suggest you first read my post about it: The BEST REACT Concept (NO CLASS NEEDED).

promo.png

If you are using a lot of event listeners with the Effect hook, useEffect, you should consider moving that logic to a custom hook. In this tutorial, we are going to create our own hook to handle those event listeners, named useEventListener.

If you'd like to see it in action, check the CodeSandbox demo.

Building our Custom Hook

Do you remember? React hooks are just functions! That's what makes them so special. Really! They are just like normal functions. We can decide what the hook takes in as arguments, and what it returns.

The only requirement is that those functions should have a name starting with use. This way it is easy to tell they are React hooks.

"Got it. Shall we start writing some code?"

Sure! As I said, let's name our React hook useEventListener.

// Our own Hook
function useEventListener(){
  ...
}

This is just a function. Nothing more.

So, let's add the arguments to our function. It should accept the event name we should listen to, the event handler to fire, and the element to add the event listener onto.

// Our own Hook
function useEventListener(eventName, eventHandler = () => null, element = window){
  ...
}

"Then, what should we do?"

Now, we should use the Effect hook, useEffect, to add our event listener logic.

useEffect(() => {}, []);

First things first. Let's make sure that our element supports the addEventListener method. If not, we return without doing anything else.

useEffect(
  () => {
    // Check if the element supports the addEventListener method
    const checked = element && element.addEventListener;
    // Stop here if not supported
    if (!checked) return;
    ...
);

Then, let's add the event listener onto our element.

    ...
    // Add event listener
    element.addEventListener(eventName, handleEventHandler);
    ...

"Hey wait! Where does the handleEventHandler method come from?"

Bare with me. We'll talk about it shortly.

Finally, the function passed to the useEffect hook should return a clean-up function. Indeed, before the component leaves the screen, we should clean-up our mess by removing the registred event listener. Let's do this.

    ...
    // Remove event listener on cleanup
    return () => {
      element.removeEventListener(eventName, handleEventHandler);
    };
    ...

"Are we done now with the useEffect hook?"

Actually, there is one more thing. Indeed, we need to tell React to run our effect only if the eventName, the element, or the handleEventHandler changes. We can do this by passing them in the dependencies array.

useEffect(
  () => {
    // Check if the element supports the addEventListener method
    const checked = element && element.addEventListener;
    // Stop here if not supported
    if (!checked) return;
    // Add event listener
    element.addEventListener(eventName, handleEventHandler);
    // Remove event listener on cleanup
    return () => {
      element.removeEventListener(eventName, handleEventHandler);
    };
  },
  [eventName, element, handleEventHandler] // Re-run if eventName, element, or eventHandler changes
);

1 minute left and we should be done with our React hook. Let's go back to the handleEventHandler method. This is the method we used as the callback to the addEventListener method.

This method should run our eventHandler in arguments. To implement it, we'll use the useCallback hook in order to return a memoized version of our eventHandler. This way it will only change if the eventHandler has changed. It allows us to prevent unnecessary renders.

import { useEffect, useCallback } from "react";
...
const handleEventHandler = useCallback(
  event => {
    if (typeof eventHandler === "function") {
      eventHandler(event);
    }
  },
  [eventHandler]
);
...

That's it! Now, you know how to create your own React hook.

"Awesome! But, could you also please show me how to use it?"

promo.png

Using our Custom Hook

Sure! Let's see how to use our useEventListener hook. I've got the perfect use case for that. I've built the following Modal component and I want to listen for click event to close the modal if the user clicks outside the component.

I have omitted the styles for readability. Check the CodeSandbox demo for that.

import React from "react";

const Modal = ({ show = false, onClose = () => null, children }) => {
  return (
    show && (
      <div className="modal-container">
        <div className="modal-content center">
          {children}
        </div>
      </div>
    )
  );
};

export default Modal;

To listen for the user clicks, we will call our useEventListener hook with the mousedown event name and pass an event handler function named handleOnClickOutside.

const Modal = ({ show = false, onClose = () => null, children }) => {

  const handleOnClickOutside = event => {
    ...
  };

  // Use our custom hook to listen for mouse down event
  useEventListener("mousedown", handleOnClickOutside);

  return (
    show && (
      <div className="modal-container">
        <div className="modal-content center">
          {children}
        </div>
      </div>
    )
  );
};

This is in the handleOnClickOutside function that we are going to check if the user clicked inside or outside the component. If yes, we will run the onClose function passed in argument to the Modal. Otherwise, we do nothing.

To check that, we need to use the useRef hook in order to access the DOM node of our Modal.

Here's how our final component should look like.

import React, { useRef } from "react";
import useEventListener from "./useEventListener";

const Modal = ({ show = false, onClose = () => null, children }) => {
  const modalRef = useRef(null);

  const handleOnClickOutside = event => {
    if (modalRef.current && !modalRef.current.contains(event.target)) {
      onClose();
    }
  };

  // Use our custom hook to listen for mouse down event
  useEventListener("mousedown", handleOnClickOutside);

  return (
    show && (
      <div className="modal-container">
        <div ref={modalRef} className="modal-content center">
          {children}
        </div>
      </div>
    )
  );
};

export default Modal;

promo.png

Comments (8)

Siddharth Vishvanath (Sid)'s photo

Wow just what I wanted to learn. Really amazing tutorial. Thank You

Show +5 replies
Siddharth Vishvanath (Sid)'s photo

Grégory D'Angelo Yeah thank you for your help. I implemented it. But the requirement was to make it as a statefull component. This approach helped me to understand. I do have other questions but it is not related to current topic. Can I contact you through message ?

Grégory D'Angelo's photo

Siddharth Vishvanath (Sid) I'm glad you did it. Sure, shoot me a message on Hashnode.