How to write custom REACT HOOKS in 5 minutes
Tags: react, javascript
Published: 2020-01-22 17:50:15

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)](https://alterclass.hashnode.dev/the-best-react-concept-no-class-needed-ck5e10due01xpqks14y3cx4qr).

[![promo.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1579172489193/d4WG5I_OX.png)](https://www.alterclass.io/)

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](https://codesandbox.io/s/alterclass-useeventlistener-12gti).

# Building our Custom Hook

[Do you remember?](https://alterclass.hashnode.dev/the-best-react-concept-no-class-needed-ck5e10due01xpqks14y3cx4qr) 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`.

```js
// 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.

```js
// 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.

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

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

```js
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.

```js
    ...
    // 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.

```js
    ...
    // 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.

```js
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.

```js
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](https://cdn.hashnode.com/res/hashnode/image/upload/v1579172489193/d4WG5I_OX.png)](https://www.alterclass.io/)

# 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](https://codesandbox.io/s/alterclass-useeventlistener-12gti) for that.*

```js
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`.

```js
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.

```js
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](https://cdn.hashnode.com/res/hashnode/image/upload/v1579172489193/d4WG5I_OX.png)](https://www.alterclass.io/)