One Router to Rule Them All: React Router

Mat Warger Development Technologies, JavaScript, React Leave a Comment

Attention: The following article was published over 6 years ago, and the information provided may be aged or outdated. Please keep that in mind as you read the post.

Previously, we looked at a very basic example of how one can benefit greatly by using community projects such as Formik to avoid the tedium of certain solutions while embracing convention to create composable and scalable applications. We will be build on that foundation to explore the objectively great library that is React Router.

React Router has been at the forefront of routing in the React ecosystem for as long as I can remember. If you’re new to React, this is the way to go when you move state and start needing more options such as parameterized routing, nesting, and conditional rendering. If you have experience with React, this brings a powerful pattern to bear, in that everything is a component. It takes the composablity of React and uses that to its benefit, handling any and all use-cases with relative ease.

In this blog, we’ll introduce the basics of the React Router through hands-on examples using its features.

Initial Details

This project was bootstrapped with Create React App. The code for this walkthrough is available here.

Instructions for use: yarn install && yarn start.

Note: I will be assuming some familiarity with React in general, including lifecycle hooks and props/state. If you need a refresher, checkout this great article here and the official React docs.

If you would like to follow along, please clone the Forms Demo Application. This will be the baseline upon which all examples will be drawn.

State Be Gone!

To begin, we need to look at a simple way that many applications start out: returning UI components based on props and state. This is the foundation of React, and so happens to be all that was necessary to get our previous application up and running.

Our list of games is fine for what it is, and searching works for us, but really, it might be nice if we could get a little more out of it – maybe a nice look at details for each game and a way to bookmark our favorites while we’re at it?

Take a look at the render method in App.js.

...
render() {
    const { formType } = this.state

    // this will do in a pinch, but should really be a router instead
    let form = null
    switch (formType) {
      case 'simple':
        form = <SimpleForm />
        break
      case 'tedious':
        form = <TediousForm />
        break
      case 'formik':
        form = <FormikForm />
        break

      default:
    }
...

We’re changing the form type based on state. As the comment notes:

// this will do in a pinch, but should really be a router instead

Let’s make that happen.

Routes

The first thing we need to do is install react-router-dom. This is the package that gives us everything we need for Routes and Links in the browser. We can accomplish this using yarn.

yarn add react-router-dom

Next we need to import the BrowserRouter and Route dependencies. At the top of the file, add the following import statement.

import { BrowserRouter as Router, Route } from 'react-router-dom'

Quick tip: The as keyword can be used to rename imports for convenience.

Next, let’s add the Routes we need to display our forms.

Remove the switch statement in the render method and remove all references to the form and formType variables. Replace the form div after the header with our new Router setup (and to clean things up just a little bit, I’ve extracted the Header into a functional component). The components should now look like this:

javascript
const Header = () => (
  <header className="App-header">
    <div className="logoContainer">
      <img src={logo} className="App-logo" alt="react logo" />
      <span className="heart">❤</span>
      <img src={formik_logo} className="logo" alt="formik logo" />
    </div>
    <h1 className="App-title">The Joy of Forms with React and Formik</h1>
    <div>
      <span
        className="form-link"
        onClick={() => this.handleChangeForm('simple')}
      >
        Simple
      </span>
      <span
        className="form-link"
        onClick={() => this.handleChangeForm('tedious')}
      >
        Tedious
      </span>
      <span
        className="form-link"
        onClick={() => this.handleChangeForm('formik')}
      >
        Formik
      </span>
    </div>
  </header>
)

class App extends Component {
  render() {
    return (
      <div className="App">
        <Router>
          <div>
            <Header />
            <Route
              path="/"
              render={() => <h3>Choose Simple, Tedious, or Formik</h3>}
            />
            <Route path="/simple" component={SimpleForm} />
            <Route path="/tedious" component={TediousForm} />
            <Route path="/formik" component={FormikForm} />
          </div>
        </Router>
      </div>
    )
  }
}

Let’s breakdown a few of these elements.

The Router block is what allows react-router to match each of the Route objects path props and display the corresponding component. These can be any React component. You can also pass a function to the children or render props that returns a component, as is done for the root (“/”) Route.

Try going to http://localhost:3000/simple to see the router in action!

Not exactly what we’re looking for…

There’s our gamelist rendered with the Route since the path matches. Huzzah!

But, there’s one thing amiss here, and that is the / route and the /simple routes are both rendering. The matching expression still works for both of them, so it’s rendering them in the order it discovers them. We would rather it be one or the other, so we can add the exact prop to the root Route to fix the behavior.

<Route
  exact
  path="/"
  render={() => <h3>Choose Simple, Tedious, or Formik</h3>}
/>

Much better.

That’s more like it!

Links

Now, if you click around you’ll notice that the navigation we had setup previously no longer works and the app will yell at you since we have gotten rid of the handleChangeForm functions. Not to worry, Link is here for you.

First, we will import the Link from react-router-dom.

import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

Next, we will replace the spans in the header with Link components, keeping the styling we had previously.

<Link to="/simple" className="form-link">
  Simple
</Link>
<Link to="/tedious" className="form-link">
  Tedious
</Link>
<Link to="/formik" className="form-link">
  Formik
</Link>

That’s it! Click your beautiful links and see the router do its thing.

For one last flourish, let’s give a little color to the active link so we know which form we’ve selected. This can be done with the NavLink component from react-router-dom. Replace the Link instances with NavLink. NavLink will allow us to apply styles when the route matches the Link.

There are two ways we can accomplish this – either with activeStyle or activeClassName props. For this simple example, we will supply a style object that will be applied for each of our links.

Create a const above the Header declaration to hold our active style color:

const activeStyle = { color: '#61dafb' }

Next, we can supply that to the activeStyle prop on our NavLink components, as shown here:

<NavLink to="/simple" className="form-link" activeStyle={activeStyle}>
  Simple
</NavLink>
<NavLink to="/tedious" className="form-link" activeStyle={activeStyle}>
  Tedious
</NavLink>
<NavLink to="/formik" className="form-link" activeStyle={activeStyle}>
  Formik
</NavLink>

Active link highlighted.

Our active link is highlighted with our nice blue color.

Attention to Detail

Now let’s see about grabbing some details for our games. We’re going to use the great Internet Games Database Api to grab info for our games.

Take a look at api.js for the implementation; I won’t go into it here (it’s simple, I promise!).

Our Simple route is all we’ll be dealing with; it will follow a common Master-detail view pattern, with the searchable list of games on the left, and a new section on the list for our details.

First, we modify the SimpleForm to hold our view, along with a route to hold our GameDetail component. If you have ever done any other sort of server or client-side routing, this will probably look familiar. If not, don’t worry. Let’s break it down.

...
<div>
  <div className="row">
    <div className="col-md-4">
      <form onSubmit={this.handleSubmit}>
        <input
          type="text"
          placeholder="Search games"
          value={this.state.searchTerm}
          onChange={this.handleSearch}
        />
        <input type="submit" value="Submit" />
      </form>
      <br />
      <GameList searchTerm={this.state.selectedSearchTerm} />
    </div>
    <div className="col-md-8">
      <Route
        path="/simple/games/:name"
        render={({ match }) => <GameDetail match={match} />}
      />
    </div>
  </div>
</div>
...

The only major change here is the Route. Like before, it has a path and a render prop that we will use to match the URL we want to use and the component we want to render. We’re destructuring the match prop from the other props that React Router gives us and passing it down for use in our detail component.

In our case, what we’re after is the :name parameter from the route’s path. This allows us to have any games referenced using this route within a route (sub-route) so we can provide a nice experience for the user. This name is provided to use by the change we need to make to the links in the GameList component. At the bottom of our GameList, we change the div to a Link component, the same as we did before (don’t forget to import it from react-router-dom as we did for the App component).

...
<div key={index}>
  <Link to={`/simple/games/${game.name}`}>{game.name}</Link>
</div>
...

Note that the Link to prop value matches the route – so we put a game’s name at the end of the route, and it matches the Route path we have provided in theSimpleForm component.

All that is left is to create the GameDetail component. You can find the code on Github – it’s a little longer so I won’t post it all here. Alternatively, you can take a look at the IGDB and implement your own detail page!

Subtle Transitions

For our final act, we’re going to add some slight transitions to make it look a little more magical – and this is the real world, so let’s be honest: we’re going to fudge some loading times with this as well so the perceived experience is better. 😉

First, let’s install a library that will help us get this done.

yarn add react-transition-group

Next, import the dependencies we’ll use:

import { CSSTransition, TransitionGroup } from 'react-transition-group'

Before we change our components, we need to add a css file that we’ll need later.

Add a file named transitions.css to /src/components and copy the following classes into it:

css
.fade-exit {
  transition: opacity 0.2s linear;
  opacity: 1;
}

.fade-exit-active {
  opacity: 0;
}

Import this at the top of SimpleForm.js.

`import './transitions.css'`

Next, we’re going to modify our render method to look like this:

render = () => {
  const currentKey = this.props.location.pathname.split('/')[3] || '/'
  const timeout = { exit: 200 }

  return (
    <div className="container">
      <div className="row">
        <div className="col-md-4">
          <form onSubmit={this.handleSubmit}>
            <input
              type="text"
              placeholder="Search games"
              value={this.state.searchTerm}
              onChange={this.handleSearch}
            />
            <input type="submit" value="Submit" />
          </form>
          <br />
          <GameList searchTerm={this.state.selectedSearchTerm} />
        </div>
        <div className="col-md-8">
          <TransitionGroup>
            <CSSTransition
              key={currentKey}
              timeout={timeout}
              classNames="fade"
            >
              <Route
                path="/simple/games/:name"
                render={({ match }) => <GameDetail match={match} />}
              />
            </CSSTransition>
          </TransitionGroup>
        </div>
      </div>
    </div>
  )
}

The TransitionGroup is what allows us to list one or more transitions and have them be managed for us. This, in turn, lets us put a CSSTransition component inside and have it manage the mounting and unmounting (and related css styles) transition animations. The key is key, here.

const currentKey = this.props.location.pathname.split('/')[3] || '/'

This tells the CSSTransition when to transition. At this point in our app, if you were to console log the pathname, you might say something like this:

/simple/games/Factorio 

The game name in this route will change, and that key change tells the CSSTransition to do its thing. The classNames prop is related to the css we added earlier. By convention, this tells the transition to prefix the event names with fade, so we end up with the css classes in our transitions.css file applying the transition animation for fading out as we change routes.

That’s it!

That’s all I have for you, but believe me, there’s plenty more to see (transition prevention, responsive routes, recursive paths, server rendering, etc). The best part is, it is components all the way down! Make sure and check out the docs for examples of everything mentioned here.

Hats off to Michael Jackson and Ryan Florence for this great library. Follow them on twitter!

I hope this example helped shed some light on the basics of React Router. If you have any feedback, please feel free to leave it in the comments below. If you need any additional assistance or training with React, Keyhole Software is happy to help – hit us up!! Thanks for reading.

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments