Article thumbnail

🔰 Detect outside click with the useClickOutside hook

6m
frontend
ui
hooks

Intro

Introducing useClickOutside - a handy hook detecting clicks outside a specific HTML element. Ideal for Modals or Sidebars, enhancing app user experience.

Understanding the use cases for the useClickOutside hook

Imagine you've built the Modal or Sidebar component that contains a backdrop behind or you have a Popover component that displays a small menu when clicking the dedicated button.

These components must be closed after clicking the backdrop area (for Modal and Sidebar) or in the case when you have Popover component, you need to detect the moment of click the space out of the Popover trigger element.

To understand it more, please check the gif below with presented situations:

Loading

The use case of hook

So as you saw, we just closed these components after clicking outside their area. Let's create the useClickOutside hook to be able to reuse such logic between different components.

How we'll use useClickOutside hook?

Look at the following code snippet in which we used our hook, and assigned ref to div element, and we passed a callback to the hook configuration that will be triggered when user clicks outside.

Loading

What is really cool? Look how TypeScript will protect us from using invalid elements.

Loading

TypeScript protection

Let's create type definitions for the useClickOutside hook

We need to define a types for the configuration object and for the stuff that will be returned by our hook.

Loading

The most important aspects:

  • we imported a type provided by React, that describes the reference object. This ref object may be null. Its value depends on what the parent component does with the ref returned by our hook,
  • the configuration object requires a callback that will be called when an outside click will be detected,
  • the passed generic type "T" must have a base of HTMLElement, if the parent component will assign the ref value to something else - it's a big mistake and our hook will not work.

Implementing the useClickOutside hook

We'll import previously created types and then, we'll write an implementation that matches created contract.

Loading

Why we've used assertion here? By saying assertion I mean the syntax as Node. It's because the contains method requires type Node but e.target has a type EventTarget | null. This causes TypeScript to yield the following prompt:

Argument of type 'EventTarget | null' is not assignable to parameter of type 'Node | null'.

Is it type-safe? Of course, it's not! We need to improve that with type-guard and when it will determine that the passed element is not a typeof Node, we'll throw an exception - our hook will not work as designed because something with unsupported shape has been passed to the event callback.

Loading

The in operator returns true if a property exists in an object. The !! operator converts the variable to bool, we're doing that because our target parameter may be an object or null. The syntax target is Node is the key here. If the created function will return true it means the type of target will be remembered by TypeScript as a Node type.

We just created our custom type-guard. Now complete implementation looks like this:

Loading

Writting the tests for useClickOutside hook

The most important aspects to test are:

  1. we need to check if the "mousedown" event is attached and removed,
  2. we need to verify if the outside click mechanism is working.

We'll use react-testing-library, the fixture pattern, and spies to perform our tests.

If you want to know what is the fixture pattern, please read following article: Creating testing fixtures.

If you want understand the spies role in testing, please read following article: Using spies in React and Typescript.

Let's start from testing the mousedown event mechanism:

Loading

Now we need to use fixture to act like the truthy component is using our hook, we don't have any other option - we want to test a real click outside situation.

Loading

Here you have the complete file with tests:

Loading

The full implementation

Repository to play with. In addition check the full implementation below if you are lazy like me:

Loading

Loading

Loading

What did we learn today?

If you're building your own design system it's really important to write code in the following way. Now, you'll be able to maintain click outside logic with simple, type-safe and tested hook.

I create content regularly!

I hope you found my post interesting. If so, maybe you'll take a peek at my LinkedIn, where I publish posts daily.

Comments

Add your honest opinion about this article and help us improve the content.

created: 20-03-2023
updated: 20-03-2023