React Integrating Routing to Material UI’s Left Nav (or Other Components)

Working with the React Material UI and trying to make routing work (or even understand how it works) with clicks on the LeftNav item items ? It’s actually quite simple once you get a hang of it. I’ll show you the code and explain the logic behind how on clicking navigation links in LeftNav you navigate to different pages/URLs in your app/website (in a snap). We’ll be using React Router, Material UI and obviously, ReactJS itself. I’ve also uploaded the code to Github.

Setup

Let’s first quickly setup the LeftNav and integrate it with the AppBar. The approach that we’ll take is, code our app with components in Node and finally create a bundle.js for the browser using the famous browserify. Oh and also we’ll be coding in ES6! Let’s kick off with our index.js:

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

import React from 'react';
import mui from 'material-ui';
import injectTapEventPlugin from 'react-tap-event-plugin';
import Router, { Route, DefaultRoute, NotFoundRoute, Redirect, Link } from 'react-router';

injectTapEventPlugin();

import App from './components/App';
import Home from './components/Home';
import About from './components/About';
import Contact from './components/Contact';

const AppRoutes = (
  <Route path="/" handler={App}>
    <DefaultRoute handler={Home} />
    <Route name="about" handler={About} />
    <Route name="contact" handler={Contact} />
  </Route>
);

Router.run(AppRoutes, Router.HashLocation, (Root) => {
  React.render(<Root />, document.body);
});

So we import our plugins, then our components too and we initialize the react-tap-event-plugin too. Finally we define our routes and their component handlers which is finally run to render our all. If you don’t understand the routing piece, feel free to refer to the docs or just copy paste code for now :-).

Now let’s define our components one by one.

// components/Home.js
import React from 'react';

class Home extends React.Component {
  render() {
    return (
      <h2>Home</h2>
    );
  }
}

export default Home;
// components/About.js

import React from 'react';

class About extends React.Component {
  render() {
    return (
      <h2>About</h2>
    );
  }
}

export default About;
// components/Contact.js

import React from 'react';

class Contact extends React.Component {
  render() {
    return (
      <h2>Contact</h2>
    );
  }
}

export default Contact;

All they do is render a heading. Finally here’s the main big piece which renders the entire app (also set as the parent Route handler in our routes configuration):

// components/App.js

import React from 'react';
import mui from 'material-ui';
import { RouteHandler } from 'react-router';

// Get mui Components
let ThemeManager = new mui.Styles.ThemeManager();
let AppBar = mui.AppBar
  , LeftNav = mui.LeftNav
  , MenuItem = mui.MenuItem;

// Define menu items for LeftNav
let menuItems = [
  { route: '/', text: 'Home' },
  { route: 'about', text: 'About' },
  { route: 'contact', text: 'Contact' },
];

class App extends React.Component {

  constructor() {
    super();

    this._handleClick = this._handleClick.bind(this);
    this._getSelectedIndex = this._getSelectedIndex.bind(this);
    this._onLeftNavChange = this._onLeftNavChange.bind(this);
  }

  getChildContext() {
    return {
      muiTheme: ThemeManager.getCurrentTheme()
    };
  }

  _handleClick(e) {
    e.preventDefault();

    this.refs.leftNav.toggle();
  }

  // Get the selected item in LeftMenu
  _getSelectedIndex() {
    let currentItem;

    for (let i = menuItems.length - 1; i >= 0; i--) {
      currentItem = menuItems[i];
      if (currentItem.route && this.context.router.isActive(currentItem.route)) {
        return i;
      }
    }
  }

  _onLeftNavChange(e, key, payload) {
    // Do DOM Diff refresh
    this.context.router.transitionTo(payload.route);
  }

  render() {

    return (
      <div id="page_container">

        <LeftNav
          ref="leftNav"
          docked={false}
          menuItems={menuItems}
          selectedIndex={this._getSelectedIndex()}
          onChange={this._onLeftNavChange} />

        <header>
          <AppBar title='MUI Routing' onLeftIconButtonTouchTap={this._handleClick} />
        </header>

        <section className="content">
          <RouteHandler />
        </section>

      </div>
    );
  }

}

App.childContextTypes = {
  muiTheme: React.PropTypes.object
};

App.contextTypes = {
  router: React.PropTypes.func
};

module.exports = App;

We render the LeftNav, AppBar and the RouteHandler (most important component to switch among Home, About and Contact). Toggling the LeftNav from the header (AppBar) is handled by our custom _handleClick() method which is fired on the onLeftIconButtonTouchTap event. That was simple, but the routing logic will require some explanation.

Routing Logic

So the first thing to notice is the onChange event of the LeftNav for which we’ve specified the _onLeftNavChange() handler. That gets called whenever a menu item is clicked which is not the currently selected one. What that handler does is, calls the transitionTo() method on the Router object with the menu item’s route value (passed as payload in the code). Once the URL changes, the callback we passed to Router.run() is fired again which does a re-rendering (virtual dom diff rendering and jazz) that shows the selected component immediately. The _getSelectedIndex() is used to fetch the menu’s selected item’s index so that it can be styled differently (active state) in the UI.

You might be wondering where does the this.context.router comes from. Notice we pass that to App.contextTypes to access that property passed down from the parent. If you don’t know what context types are, then read this article.

This logic was taken from material ui’s documentation source.

Now that you know how the LeftNav menu items can be integrated with react-router, it should be super simple to do the same with other components of material UI like a button or a tab. All you’ve to do is find the click/active/change/etc. events, access `router` by specifying it in the contextTypes and call the transitionTo() method on the Router. The tabs component even has an example.

Cheers!

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

2 thoughts on “React Integrating Routing to Material UI’s Left Nav (or Other Components)”

  1. Great walk through! It was exactly what I was looking for.

    One thing, you don’t actually provide a link in the following paragraph

    You might be wondering where does the this.context.router comes from. Notice we pass that to App.contextTypes to access that property passed down from the parent. If you don’t know what context types are, then read this article.

Leave a Reply

Your email address will not be published. Required fields are marked *

*