blog.karenying.com
blog.karenying.com

Simple React Router Nav Bar

Using React Router and clever CSS to create a nav bar with dots to show hover and active state

Nov 22, 20204 min read

react router

One of my favorite things is simple UI state indicators. The nav bar on my personal site utilizes dots under the links to convey hover and active state. It’s minimalist yet effective:

karen nav barMiss me with the dark blue/purple visited links 🤮 Please visit my site to see a demo…this GIF quality is so bad

In this tutorial, we walk through how to code this nav bar.

Implementation

-1. Prereqs

This tutorial assumes you have some knowledge of JavaScript and React. All good? Let’s get started 👍🏼

0. Getting Started

We’ll use Create React App to create, bundle, and run the project:

$ npx create-react-app nav-bar-dot
$ cd nav-bar-dot
$ npm start

1. Adding Pages

Let’s add three pages: home, about, and contact. We’ll keep it simple and only render the title of each:

App.js
const Home = () => (
  <div>
    <h2>Home</h2>
  </div>
);

const About = () => (
  <div>
    <h2>About</h2>
  </div>
);

const Contact = () => (
  <div>
    <h2>Contact</h2>
  </div>
);

2. Routing Pages

Now we need to route them. We’ll be using React Router Dom:

npm install react-router-dom

As per the quick start tutorial, we wrap all our Routes in a Router component. We’ll route both the root URL and /home to the Home component.

App.js
import { BrowserRouter as Router, Route } from 'react-router-dom';

function App() {
  return (
    <div className='App'>
      <Router>
        <Route exact path='/' component={Home} />
        <Route exact path='/home' component={Home} />
        <Route exact path='/about' component={About} />
        <Route exact path='/contact' component={Contact} />
      </Router>
    </div>
  );
}

If we run the app, and manually visit http://localhost:3000/about, we should see the About page.

3. Creating Nav Bar

We then create a new file called Header.js and a new component called HeaderLink inside it. Each HeaderLink will route to the page it’s passed:

Header.js
import { Link } from 'react-router-dom';

import './Header.css';

const HeaderLink = ({ page }) => {
  const title = page.charAt(0).toUpperCase() + page.slice(1);

  return <Link to={`/${page}`}>{title}</Link>;
};

Here, we use React Router’s Link component. The to prop accepts a string that’s the path. In our case, it will be either 'home', 'about', or 'contact'.

Now we’ll create our parent component called Header which calls HeaderLink with all our pages:

Header.js
const Header = () => {
  return (
    <div className='header'>
      <HeaderLink page='home' />
      <HeaderLink page='about' />
      <HeaderLink page='contact' />
    </div>
  );
};

export default Header;

If we toss our HeaderLink component into App.js, we should see a super plain nav bar that works as expected:

bare bones nav bar
Bare bones nav bar

Let’s style it a bit:

Header.js
<Link to={`/${link}`} className='headerlink-title'>
  {title}
</Link>
Header.css
.header {
  display: flex;
  justify-content: center;
}

.headerlink-title {
  color: black;
  text-decoration: none;
  padding: 0 5px;
}

And we should have something slightly better:

slightly styled nav bar
Slightly styled nav bar

Yay we just created a fully functioning nav bar. Now we can move onto the fun part: adding a hover and selected state.

4. Adding Hover/Selected Indicator

In order to know which page the user is currently visiting, we use route params.

To render the Header component, instead of setting a fixed path, we append our page param (variable name), to a colon. This lets React Router know that page is the variable name for the path.

App.js
<Route path='/:page' component={Header} />

And we add this new Route to App.js:

App.js
<Router>
  <Route path='/:page' component={Header} />

  <Route exact path='/' component={Home} />
  <Route exact path='/home' component={Home} />
  <Route exact path='/about' component={About} />
  <Route exact path='/contact' component={Contact} />
</Router>

In order to grab this page variable in our Header component, we use the useParams hook.

useParams().page will return the value of our page variable, which is also the slug after the root URL. With this info, we can pass a selected prop to HeaderLink:

Header.js
import { Link, useParams } from 'react-router-dom';

const Header = () => {
  const { page } = useParams();

  return (
    <div className='header'>
      <HeaderLink page='home' selected={page === 'home'} />
      <HeaderLink page='about' selected={page === 'about'} />
      <HeaderLink page='contact' selected={page === 'contact'} />
    </div>
  );
};

so that HeaderLink knows if its link is selected or not.

We currently have a slight bug. If we visit the root URL (usually http://localhost:3000), the nav bar doesn’t show up. Why? Because path='/:page' doesn’t apply since page is null.

We can catch that by hardcoding Header to show up for the root path:

App.js
<Router>
  <Route path='/:page' component={Header} />
  <Route exact path='/' component={Header} />

  <Route exact path='/' component={Home} />
  <Route exact path='/home' component={Home} />
  <Route exact path='/about' component={About} />
  <Route exact path='/contact' component={Contact} />
</Router>

Since useParams() wouldn’t return anything in this case, we have to tweak the page variable in Header.js to default to 'home':

Header.js
const page = useParams().page || 'home';

Great, with that taken care of, we can finally style our hover and selected state!

Let’s add some CSS classes to our HeaderLink component:

Header.js
<Link to={`/${page}`} className='headerlink-title'>
  {title}
  <div className={selected ? 'headerlink-dot-active' : 'headerlink-dot'}></div>
</Link>

And their respective properties:

Header.css
.headerlink-dot {
  margin-top: -0.5rem;
  opacity: 0;
  transition: opacity 200ms linear;
}

.headerlink-title:hover .headerlink-dot {
  opacity: 1;
}

.headerlink-dot-active {
  margin-top: -0.5rem;
  display: block;
}

This CSS allows the dot to show up when headerlink-title is hovered over. The transition property of .headerlink-dot lets the appearance look smoother.

If we play around with our new styled nav bar, we should see this:

demo

It looks so nice and smooth 😊

One final detail: we don’t want cursor: pointer or any click events for the HeaderLink of the currently selected page.

We can easily fix that with the addition of another class:

Header.js
  let className = selected ? 'headerlink-no-link ' : '';
  className += 'headerlink-title';

  return (
    <Link to={`/${page}`} className={className}>
Header.css
.headerlink-no-link {
  pointer-events: none;
}

And we’re officially done 🎉

Conclusion

In this tutorial, we learned how to set up a nav bar with React Router. Then we added a hover/active state as visual feedback to the user.

If you got lost along the way, check out my GitHub repo. You can also see the live version here.

If you decide to deploy your React app with Netlify, read about enabling redirects on Netlify to fix client-side routing for SPAs here.

Thanks for reading. Happy hacking!