A dropdown button with React (beyond the toggle)

Alex Suarez
5 min readSep 5, 2019

If you are like me, that kind of developer who loves to create your own components totally from scratch whether to have more control over it, or to absolutely know how it was made and where to refactor it, its possible you’ll find yourself in front of the “Button w/ dropdown” component or the “input with dropdown” component.

In this article, I will show the way I develop this kind of component in React… everything begins with the component “atoms” definition, atoms are also components itself.

Button

Dropdown card

Parent Component (to wrap both)

Let’s use Tailwind to accelerate styling if you haven’t heard about Tailwind please take a look at the project website … and thanks me later. Ok, show me the code.

Button atom

Dropdown card atom

This component is expecting a simple array of strings to render the list (a simple unordered list), we will pass a sample array now from the parent component. Of course, we can split the code a bit more and create also a component for LI elements, but I decided not.

Parent component

Ok, we have what we need, let's hide the card by adding an initial state in false in our parent component and wrapping the “DropDownCard” component into an inline condition

Now our list is hidden, and we are going to use the “setOpen” method to change the “open” value on every click at our button, using its prop “onClick”.

Now with this, we have a nice dropdown button but… it behaves as a toggle if you click outside the “DropDownCard”, you will notice the dropdown will keep open until you click again in the button, this is not bad but is not the natural behavior since user expects the dropdown hides if the click is outside the card.

So let’s solve that, you can do this adding two solutions one with vanilla, the other one using a more “React-ive” approach.

Whether you decide one or the other we need to create a life cycle as a React Effect to listen to the click events after our component mounts and a function to get the clicked element. The “thing” is gonna happen inside the “handleClick” function. Let’s take a look at both approaches, but before this is our parent component so far.

Oh vanilla, vanilla!!

For the vanilla approach let’s add an ID to our parent component div, and then in the “handleClick” function check if the clicked element is not closest to the ID like this:

Now our dropdown is working as a real dropdow, it opens and closes if you click the button but also it closes if you click outside the dropdown anywhere in the document.

This is cool but why using vanilla in React? Also, we need to explicitly know the ID value to use it inside the “handleClick” function. What if we need more that one dropdown button component in the same UI at the same time? Then we have to use the same ID on each of them or worse force them to use a different ID and dynamically pass the ID to the “handleClick” function for each case, in other words, a simple dropdown becomes a nightmare and no, lucky for us we have a more dynamic approach using a totally React base solution.

The React Ref-actoring.

Let’s use the UseRef React hook to solve this once and for all, initialize a const as a React.useRef(null),

Ex. const drop = React.useRef(null)

then remove the id from our parent component and add the class “dropdown” or “Dory” if you like “Finding Nemo”, then let’s change the evaluation now from

!e.target.closest(“#drop-down-wrapper”) && open

to

!e.target.closest(`.${drop.current.className}`) && open

Since closest always needs a selector and not the element itself we need to pass a selector from our parent component we do so accessing to the “className” from “current” from our Referred element and remembering placing the “.” in from of it indicating that it’s a selector.

Now our final solution looks like this:

You may also want it to pass the “setOpen” method down to the “DropDownCard” and call it at the “onClick” in your list item, to close the dropdown when clicking at any list item, go ahead and take a look at CodeCandbox to check the entire code and let me know what you think, one last thing, no matter what, please, please don’t name your classes “Dory”.

--

--

Alex Suarez

Senior UI/UX Designer & Frontend Engineer. Sendtinel Co-Founder, Frontend Engineer at Everymundo.