Make a navbar with dropdown, outside click detection and other features in React using hooks

HotSurface
6 min readOct 27, 2019

--

Major Edit: I found two beautiful videos which will do something similar to what I have done and both are by the amazing Maximilian Schwarzmüller:

Please find the links:

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — -

Why would you still want to read my article below:

I have implemented a generic navbar that has dropdown and links in one itself, outside click and React Hooks.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — —

After searching the length and breadth of the likes of StackOverflow, Github, and other forums, I found a lot of various frameworks that will implement a top navbar for you. But how do you roll your own?

There is no one solution fit all here. But you can follow my instructions and make a basic one which will give you a good optimal UI/UX experience.

There a few things which constitute a good navbar in my opinion:

  1. Top position navbar
  2. Dropdown functionality with multiple horizontal rows. Any eCommerce website you see will have this. For example, Levis website got it

3. Ability to select from the dropdown, you should be able to navigate to a new page and the dropdown should hide.

4. When you click on other links or even when you click outside, the dropdown should go away

Then there are some good to have’s, which I do not have made provision because they are a heavy focus on CSS.

5. Responsive navbar, when the screen is small, they should display a hamburger

6. The active link should have some sort of identification

7. Search button

— — — — — — — — —

So let's get started:

Pre-req’s -> Installed Node, and Create-react-app.

Let's start by creating a new project.

Go to your terminal and create a new project called navbar-react

$ create-react-app new-portfolio

Open this project on VScode or editor of your choice. I do it on VS Code because I really like the Draculla Theme (https://draculatheme.com/visual-studio-code/)

Remove the generic code from App.js and App.css. So after this step this is how your App.js look like. This will be my first commit in the repo too.

import React from 'react';import './App.css';function App() {return (<div></div>);}export default App;

We make a new folder called Componentsunder src and create a new file called NavBar.js.

Install material UI to give your app structure.

npm install --save @material-ui/core

import React, * as react from 'react';import { makeStyles } from '@material-ui/core/styles';import Paper from '@material-ui/core/Paper';import Grid from '@material-ui/core/Grid';const NavBar = () => {return (<ul>   <li>Projects</li>   <div>     <Dropdown />   </div>   <li>Blog</li>   <li>Skills</li>   <li>Story</li> </ul> );};export default NavBar;

So, as you can see we have 4 tabs, the first one is a dropdown where we want to call Dropdown` component (which we will code later) when the user clicks on the first tab.

Now to declare Dropdown Component, I have declared it inside the NavBar.js itself, but you can feel free to do it outside.

const Dropdown = () => {  return (   <Grid container>     <Grid item xs={12} sm={2}>       {''}     </Grid>     <Grid item xs={12} sm={2}>      <h4>Web Dev</h4>      <ul>        <li>D3 World Map</li>        <li>Keppler</li>        <li>P5.js</li>        <li>Gatsby</li>     </ul>    </Grid>    <Grid item xs={12} sm={2}>      <h4>Data Science</h4>      <ul>        <li>Data Mining</li>        <li>Django web app</li>        <li>Kaggle</li>        <li>US Phone Complaints</li>     </ul>    </Grid>    <Grid item xs={12} sm={2}>      <h4>Design</h4>      <ul>        <li>Posters</li>        <li>Sketch</li>     </ul>    </Grid>    <Grid item xs={12} sm={2}>      <h4>Video</h4>      <ul>        <li>Youtube Vlog</li>      </ul>    </Grid>    <Grid item xs={12} sm={2}>     <h4>Misc</h4>    </Grid>  </Grid>  );};

Now since we have all the content in, we need to get started with the functionality.

First things first, we need the nav-button to dropdown on click and some nav-button to directly take you to a different page.

To achieve dropdown, we will leverage the power of hooks and will use the state hook.

In the NavBar component declare state hook as follows:

const [listOpen, setListOpen] = react.useState(false);

Then the link we want to be the dropdown shall have the following:

onClick={()=>setListOpen(!listOpen)}

We will make the Dropdown component to be wrapped inside a ternary condition as follows

{listOpen?<Dropdown />:null}

We would also like the dropdown to close when we click on any other link, hence we should add the following:

onClick={()=>setListOpen(!false)}

So the component will look like:

const NavBar = () => {  const [listOpen, setListOpen] = react.useState(false);  return (    <ul>      <li onClick={()=>setListOpen(!listOpen)}>Projects</li>      <div>        {listOpen?<Dropdown />:null}      </div>      <li onClick={()=>setListOpen(false)}>Blog</li>      <li onClick={()=>setListOpen(false)}>Skills</li>      <li onClick={()=>setListOpen(false)}>Story</li>    </ul>  );};

Now we need to add pages as different links to the pages. In order to do so we will import react-router-dom library.

npm install --save react-router-dom

Let's start by adding <Router/> around the <App/> in index.js

ReactDOM.render(<Router><App /></Router>,document.getElementById('root');

In App.js, add the routes information to the App function:

function App() {  return (    <div>      <NavBar />      <Switch>        <Route exact={true} path="/" component={Home} />        <Route path="/home" component={Home} />        <Route exact path="/blog" component={Blog} />        <Route exact path="/skills" component={Skills} />        <Route exact path="/story" component={Story} />     </Switch>   <p> FOOTER</p>  </div> );}

To the Navbar component, add the Link information in the li section, so now the <Navbar/> component will look like:

const NavBar = () => {  const [listOpen, setListOpen] = react.useState(false);  return (    <ul>      <li onClick={() => setListOpen(!listOpen)}>Projects</li>        <div>{listOpen ? <Dropdown /> : null}</div>      <li onClick={() => setListOpen(false)}>        <Link to="/blog">Blog</Link>      </li>     <li onClick={() => setListOpen(false)}>       <Link to="/skills">Skills</Link>     </li>     <li onClick={() => setListOpen(false)}>       <Link to="/story">Story</Link>     </li>   </ul>  );};

We would like to add logo to the Navbar which will show up on the left and the rest of the links on the right. So add the logo image under the public folder and add the img in your Navbar component by adding the following above the <ul>:

<Link to="/"><img src={process.env.PUBLIC_URL + '/logo.png'} alt="Logo" /></Link>

Now we have a complete functional Navbar with Dropdown and routing. We need to do some css for the navbar so that it actually looks like a navbar now. So, I created a NavBar.style.css

.navbar {  padding: 1% 4% 1% 9%;  cursor: pointer;  color: #0544f9;}.logo {  height: 100px;}.navbar ul {  padding-left: 500px;  max-width: 100%;  overflow-x: hidden;  transition: 0.5s;  border: none;}.navbar ul li {  background-color: rgba(0, 0, 0, 000);  border: 0;  color: #0544f9;  cursor: pointer;  display: inline-table;  font-size: 1.5rem;  height: 3.28rem;  line-height: 3.28rem;  padding: 0 1.14rem;  text-align: center;  border-style: none;}.dropdown {  position: fixed;  margin: 5%;  top: 100px;  width: 70%;  background-color: #ebebeb;  color: #0544f9;  cursor: pointer;  opacity: 100;}.dropdown li {  padding: 1% 12% 8% 2%;  color: #0544f9;  }

Finally, we add links to the dropdown links:

<ListItem>  <Link to="/d3-world-map">D3 World Map</Link></ListItem>

Now, its time for some ‘some good to have’s’.

First things first, when we click on the dropdown item, it navigates us to a new page but we want to close the dropdown when you are on the new page. This should be easy right as we should just put listOpen to false, right? Well yes! that is the way to do it except listOpen is not available in Dropdown component. Hence, we will have to send a callback set listOpen to false when we click on the link.

In the Navbar component set a callback method called myCallback with listOpen as a parameter.

const myCallback = (listOpen) => {  setListOpen(listOpen);};

Declare a new prop when returning Dropdown component.

<Dropdown callbackFromParent={myCallback} />

Now, in the Dropdown component, pass the newly declared prop and send back listOpen as false.

const Dropdown = ({ callbackFromParent }) => {
.
.
.
<ListItem onClick={() => callbackFromParent(false)}>
<Link to="/d3-world-map">D3 World Map</Link> </ListItem>
.
.
.
}

Now, when the dropdown is active and you do not want to make any selection, you should be able to click anywhere outside and the dropdown should go away. This is very well explained in this brilliant codesandbox article.

https://codesandbox.io/s/w62xl39907

All we need to do is, add a reference to the node in the Dropdown component.

<Grid container ref={node}>...</Grid>

Then we just useRef hook to understand and capture click location. If it is inside, then we return nothing but if it is outside, we set listOpen to false.

const Dropdown = ({ callbackFromParent }) => {  const node = react.useRef();  const handleClick = (e) => {    if (node.current.contains(e.target)) {      // inside click      return;    }    // outside click    callbackFromParent(false);  }; react.useEffect(() => {  document.addEventListener('mousedown', handleClick);  return () => {    document.removeEventListener('mousedown', handleClick);  }; }, []); return(...);
}

All the code is available on my Github page and feat/navbar branch.

Please find below the complete NavBar component:

Hope you could find this helpful. Please feel free to message/email if you want to change anything or have questions.

--

--

Responses (2)