React Hooks — How to use Hooks conditionally (Click Outside example)

Hermenegildo Isaias
4 min readJul 7, 2019

With React 16.8 officially introducing React Hooks, developers have started to make use of Hooks in production ready applications (myself included). It is an incredibly powerful addition to React. I’ve enjoyed playing around with Hooks and I see it changing the frontend landscape.

However, it does have a few gotchas that we need to consider. The React docs state the following rules for Hooks:

  • Only call Hooks at the top level. Don’t call Hooks inside loops, conditions, or nested functions.
  • Only call Hooks from React function components. Don’t call Hooks from regular JavaScript functions. (There is just one other valid place to call Hooks — your own custom Hooks. We’ll learn about them in a moment.)

This article primarily tackles not using hooks within conditions and a possible workaround.

// 🔴 We're breaking the first rule by using a Hook in a condition
if (name !== '') {
useEffect(function persistForm() {
localStorage.setItem('formData', name);
});
}

See React Docs: https://reactjs.org/docs/hooks-overview.html

This is all from the React docs at this point, so we going to jump to a practical example where one would need/want to conditionally make use of Hooks.

Creating a custom Dropdown

We want to create a custom dropdown because we’re A1 developers. The requirements are simple, nothing special about this dropdown.

  • I click the dropdown, and I see all the options
  • I click an option item to select it.
  • If the dropdown is expanded and I click outside the dropdown, it should automatically close. In order to better illustrate the conditional portion of the code, I made use of a counter instead of just closing the dropdown.

Class Implementation

Dropdown without click outside event

If you see the gif above, you’ll notice clicking outside does not collapse the dropdown. So let’s add that logic old school style.

We’ll need to add event listeners to detect when to collapse the dropdown items.

click outside event listener added

So there you go, we’ve got our event listener working! Dope stuff. However, I did start off by saying we’re A1 developers and the above event listener just doesn’t cut it for me. You’ll notice that the event is triggered as soon as the dropdown is in view. Therefore, if this were a form with 5 dropdowns, we would have 5 event listeners firing on every mouse click. Ouch!

I am not one to prematurely optimize code. At least, I try not to be, so I understand it’s not the end of the world. But, if it was the end of the world, surely, we can make this better? I mean, why should I add an event handler as soon as the dropdown is rendered?

The way I see it, I only care about click events when the dropdown children options are open.

Start Listening Conditionally

Works something like this

Hook Implementation

I want to fast forward at this point and steal code from useHooks.com.

// Hook
function useOnClickOutside(ref, handler) {
useEffect(
() => {
const listener = event => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}

handler(event);
};

document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
// unmount
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
},
[ref, handler]
);
}

You’ll notice, with hooks, we are given the ability to completely separate clicking outside elements from our code. We’ve written code for our dropdown with no mention or attachment to our dropdown. Amazing right?! I see the promise land mama.

Let’s add a dropdown without the click outside hook but include the more popular hooks shipped with the React library.

Jumped straight into it with React Hooks. I would say a few words about how small and neat hooks makes things but that’s beside the point of this article. Once you’ve digested the above, let’s look at using the hook from useHooks.com (code snippet attached above).

The above code gives us the same initial problem we had with classes when adding events as soon as the component is mounted.

How do we get around this problem?

We can’t do this:

// Breaking React Hook Conditional Rule
if(opened) {
useOnClickOutside(ref, onClickOutsideHandler);
}

Well, the more I think about it. The more I realise that theuseClickOutside hook is really a construct of the dropdown items (options) and not the dropdown itself. As soon as I see dropdown items visible, then I want to start handling clicking outside of the dropdown to dismiss the items when the condition is met.

I just need to figure out how to use my new shiny hook lower down.

I started by doing this.

const ClickOutside = ({ parentRef, onClickOutsideHandler, children }) => 
{
useOnClickOutside(parentRef, onClickOutsideHandler);

return (<React.Fragment>{children}</React.Fragment>);
};

This is just another Pure Component, nothing special. No conditions used here. Since I know a Fragment doesn’t render anything in a dom tree, I won’t have to worry about aligning with whatever consumes this Pure Component.

Therefore, I can do the following to achieve only adding events when the dropdown items are visible.

If you got this far, thank you for your patience. Here is the working example:

--

--