Creating a Collapsible Navigation Menu in React.js

Creating a Collapsible Navigation Menu in React.js

Updated 8/15/2016

Today we’re going to be making a collapsible navigation menu solution using nothing but React.js as an exercise in using event handlers, component state, and various React lifecycle methods. There is a much simpler and more elegant solution to achieve this exact same thing – my thanks go out to Mike for sharing his example in the comments below. But for the sake of practice and education, let’s go ahead and dig in.

We’ll be sticking mostly to the React portion here, but there was some SCSS work that I did to make the navigation pretty to look at, but I’ll leave you to do most of that.

Getting Set Up

Really the only true dependency that we need here is React itself, but I am going to recommend (and use) react-icons material design icon font to give us access to navigation icon, as well as react-router to get your router connected. So install those dependencies:

$ npm install react-router

Next let’s get these imported into a new file that we’ve named navigation.js inside the components directory of our app.

import React, { Component } from 'react'; 
import { Link } from 'react-router'

Get your navigation component created, then we’re ready to get to the fun parts. Everything we’ll be making below will go inside this component and above the render()function.

export default class NavContainer extends Component {

//our other functions, and state will go here soon

render() {
  <div className="nav_container">
  <div className="site_title"><Link to="/">WEBSITE TITLE</Link></div>
    //navigation will go here

Set our initial state and get window width

Let’s look ahead a bit and see what we’re going to need for this to work;

We’re going to need an if-then statement to see if it’s time to render the mobile navigation menu or the desktop one.
We’ll need a way to tell if the mobile navigation is open or not.
So let’s get our component’s state declared with these:

constructor(props) {
this.state = {
    windowWidth: window.innerWidth,
    mobileNavVisible: false

After we’ve set the initial window width and told the component that the mobile navigation isn’t visible right now, let’s allow our state to update itself if the user changes the screen size.

handleResize() {
  this.setState({windowWidth: window.innerWidth});

componentDidMount() {
  window.addEventListener('resize', this.handleResize.bind(this));

componentWillUnmount() {
  window.removeEventListener('resize', this.handleResize.bind(this));

These three functions will set up an event listener once the component has mounted, and then it handles it to update our state with the new window width if it changes. This is going to allow us to test if it’s time to draw the mobile navigation or the full one.

Full navigation menu

Next let’s create a function that will return only the navigation menu. Creating a single function that handles the links to our menu will make changing them a breeze, and keeps us from repeating ourselves, since both the full and mobile menus will feature the same links.

navigationLinks() {
  return [
      <li key={1}><Link to="about">ABOUT</Link></li>
      <li key={2}><Link to="blog">BLOG</Link></li>
      <li key={3}><Link to="portfolio">PORTFOLIO</Link></li>

Mobile navigation menu

Make another function that asks if the mobile menu is visible or not, and display these links based on the answer, then create a click handler for when the mobile menu button is clicked (or touched):

renderMobileNav() {
  if(this.state.mobileNavVisible) {
    return this.navigationLinks();

handleNavClick() {
  if(!this.state.mobileNavVisible) {
    this.setState({mobileNavVisible: true});
  } else {
    this.setState({mobileNavVisible: false});

Tying the collapsible menu together

Lastly, we’re going to put it all together with this function that will render either the mobile navigation or the full one:

renderNavigation() {
  if(this.state.windowWidth <= 1080) {
    return [
      <div className="mobile_nav">
        <p onClick={this.handleNavClick.bind(this)}><i class="material-icons">view_headline</i></p>
  } else {
    return [
      <div key={7} className="nav_menu">

We’re simply running an if then statement that checks if it exceeds the width that we’ve defined, and returning either a div containing the mobile navigation with the material icon that will pass click events to our handler, or the full navigation.

Notice that each of these menus are nested inside a div with a name. Thanks to SCSS we can style the same ul of items with two completely different looks to fit the needs of our mobile and desktop navigation.

You’ll now only to need to call this function inside the render() function of our component and you’re done!

render() {
    <div className="nav_container">
      <div className="site_title"><Link to="/">WEBSITE TITLE</Link></div>

Hopefully this exercise has helped you to learn some things about event listeners and React lifecycle methods. Like I said above, this is not the most efficient way to accomplish a collapsible navigation menu, but could be useful in some cases as a point of reference. Since completing this tutorial back in May I’ve seen lots of awesome solutions that you should check out. One of the commenters below left a great one that I thoroughly enjoy and I recommend checking it out if you’re looking for a production ready solution to your applications navigation needs.

Thanks for reading, and sharing, and please leave any questions or comments you may have below. Until next time, happy coding!

You can take a look at the complete source code for this project here.

By | 2016-05-13T23:30:23+00:00 May 13th, 2016|Development, Javascript|16 Comments

About the Author:

  • sibiru

    could you share full source code please, thanks

  • Lukáš Unzeitig

    any demo to show the result?

  • Ilshat Safiullin

    Every time when bind called it returns different functions. So, removeEventListener in componentWillUnmount doesn’t remove actual handler and this can cause memory leak. You may save binded handler in class instance or use handleResize = () => {…} pattern.

    • Thank you! I appreciate this. For some reason the memory leak issue hadn’t crossed my mind. I’ll be going back shortly and correcting this.

      • In the constructor, this.handleResize = this.handleResize.bind(this); … or you can add decorators transform and use any of several bind/autoBind decorator implementations.

      • AndriyK

        @emeentsmedia:disqus @tracker1:disqus has this been corrected in the example code? I am not as familiar on the bind.this() feature so just want to make sure I am doing this right.

        Does adding the below code to constructor of the example code fix the potential problem or what else do I need to do?
        — this.handleResize = this.handleResize.bind(this);

        example code:

        • No, I didn’t have a chance to correct this. But what you added should resolve any issues with the memory leak that Tracker mentioned.

  • G. James Carrow

    Cool man. thanks for this. I had to put a return statement in the render function, which I didn’t see in your tutorial, but other than that it worked perfectly.

    • Yup you’re exactly right, thanks for pointing that out to me!

      • G. James Carrow

        No sweat. It’s the least I can do. I’m all over your react tutorials. Thanks so much for taking the time to put these out. I’ve been running around from tutorial to tutorial trying to find a decent explanation of the basics of these different technologies, ie,; react, webpack, react-router, redux. Somehow yours are the clearest, most cohesive I’ve found yet. And I’m sure I’m not alone in this sentiment. Thanks so much!

        • Well thanks James, I really appreciate your compliments! I am glad they are helpful to you!

  • Mike

    The following code fully accomplishes the same thing with no dependencies. Using React for this is insane.

    @media (max-width: 1080px) {
    .nav li { display: none; }
    .nav:before { content: “2261”; font-size: 30pt; }
    .nav.visible li { display: block; font-size: 12pt; }

    <a href=”/about”>ABOUT</a>
    <a href=”/blog”>BLOG</a>
    <a href=”/portfolio”>PORTFOLIO</a>

    document.querySelector(‘.nav’).addEventListener(‘click’, e => {‘visible’);

    • This is indeed a very clean solution to achieve exactly the same thing as I did without React. I appreciate you sharing it!