A tutorial about how to detect a click outside of a React component by creating a custom React hook for it. For example, you may want such custom React hook for various components like a dialog or dropdown, because they should close when a user clicks outside of them. So we need a way to find out about this outside click.
Much of what you will learn here goes back to the concept of event bubbling and capturing in JavaScript. So if you need a refresher on the bubbling, target, and capturing phases, Iâd suggest you to read the following article before which addresses this topic for React.
Letâs kick things off with a function component in React where we increment a counter by using Reactâs useState Hook and an event handler:
Everything works as expected. Next we want to reset the state (here: count ) whenever a user clicks outside of the button. We can write the event handler for resetting the state, however, itâs not clear yet where to use it:
A naive approach would be using this new handler on the outermost HTML element of the top-level component (here: <div> ). However, a better approach would be using this event handler on a document level as a best practice, because the outermost HTML element can change during the development process.
We will implement this in a custom hook straightaway to avoid a redundant refactoring:
The custom hook initiates a React ref which gets returned eventually. Itâs not really used yet in the hookâs implementation details. In addition, the custom hook uses Reactâs useEffect Hook to assign (and remove) an event listener (here: click event) on document level. After all, whenever the document gets clicked, the handler and thus the passed callback function will run.
Now the custom hook can be used the following way in our React component: pass the event handler as callback function to the hook â which executes whenever the document gets clicked. In addition, use the returned reference (here: ref ) and assign it to the button HTML element:
However, as you will notice, the handler will always fire, also when the button itself gets clicked. If you check the custom hook again, you will see that the reference (read: ref ) is not really used in there. What we want to accomplish: Execute the callback function only when anything outside of the passed ref (representing the button here) is clicked, not when the ref itself (or its content) gets clicked:
Thatâs it. The reference assigned to the button is the border between triggering the buttonâs event handler and the documentâs event handler. Everything clicked thatâs outside of the reference will be considered as an outside click.
There is a small improvement missing though: What if we need to stop the event bubbling for certain edge cases by using the stopPropagation() method on an event handler. For example, in the following we extend the component with a click on the container element and stop the propagation of the event there:
When we try this example, we will see that the click on the container does not go through as âoutside clickâ, because even though it is an outside click, it never reaches the documentâs event listeners due to the event being stopped from bubbling.
By making use of the bubbling and capturing phase, we can adjust the custom hook to fire on the capturing phase instead. Because the capturing phase happens before the bubbling phase, the click on the document will run always even though events are being stopped from propagating in the bubbling phase:
Thatâs it. You created a custom hook which detects clicks outside of referenced components/elements. Again, read through the event bubbling and capturing article to get a more in-depth explanation of whatâs going on in these phases.
Last but not least, you may want to fall back to a library to deal with this subject. You can always implement custom hooks yourself â itâs a good way to exercise and a to understand implementation details under the hood â however, if there is a bullet proof library out there which manages all the edge cases (see the capturing/bubbling edge case from before), you should make use of it.