React Router: Add the Power of Navigation

React Router: Add the Power of Navigation

Let’s make some navigational React components with React Router

In this article we’ll cover:

I’ll walk you through React Router as if you were coding alongside me, so open up your code editor if you’ve got one!


What is React Router?

React Router is a library that lets you delineate your routes on the front-end, so a user sees one component of your choice on localhost:3000/homepage, another on localhost:3000/faq, and others on /links, /contact, etc. You can make a React app already, but clicking around now is the name of the game.


Setting up your first routes

Let’s write three components in a components.js file in src that we’ll later put on different ‘pages’ or routes. (Your compiler may whine that you don’t need to import React, but we will need it soon.)

import React from "react"

 const Homepage = () => {
  return "Welcome to my website about my pets."
}

const Dog = () => {
  return "My dog Thundric is great. I couldn’t decide between Thunder or Electric. Don’t call him Rick."
}

const Cat = () => {
  return "I love my cat Smalls. He sure is hefty."
}

export { Homepage, Dog, Cat }

Soon we’ll be able to access each component like so:

localhost:3000/ localhost:3000/dog localhost:3000/cat

Next we make a Routes page in our src directory. Let’s name it routes.js.

Make a component called Routes and npm install ‘react-router-dom’.

import React from "react"

const Routes = () => {
  return <div></div>
}

export default Routes

Now let’s write our routes.

First we import Route:

import { Route } from "react-router-dom"

Then on each line of the Route tag, we choose a pathname, followed by the component.

<Route path="/path-name-goes-here" component={ComponentName} />

Your routes page should look something like this. We have a very important attribute called ‘exact’ in our Homepage route; we’ll go over it right after we’re set up.

import React from "react"
import { Route } from "react-router-dom"
import { Homepage, Dog, Cat } from "./components"

const Routes = () => {
  return (
    <div>
      <Route exact path="/" component={Homepage} />
      <Route path="/dog" component={Dog} />
      <Route path="/cat" component={Cat} />
    </div>
  )
}

export default Routes

Now let’s go to index.js and edit our ReactDOM.render() function so it accesses our Routes instead of create-react-app’s default App component. Don’t forget to import your Routes page.

Import { Router } from ‘react-router-dom’ and have Router tags encase our Routes page.

Finally, in order for us to track our navigation, import createHistory from ‘history/createBrowserHistory’, assign createHistory() to a variable named history, and have history be the ‘history’ attribute of Router.

Your routes file should look like this:

import React from "react"
import ReactDOM from "react-dom"
import "./index.css"
import Routes from "./routes"
import { Router } from "react-router-dom"
import createHistory from 'history/createBrowserHistory'
import * as serviceWorker from "./serviceWorker"
const history = createHistory()

ReactDOM.render(
  <Router history={history}>
    <Routes />
  </Router>,
  document.getElementById("root")
)

serviceWorker.unregister()

Run npm start to see where you’re at.

You should be able to access each of your components at their respective paths by manually typing them in (we will write links with Link soon):

localhost:3000/ localhost:3000/dog localhost:3000/cat


The importance of using ‘exact’

Let’s go over the incredible importance of the ‘exact’ attribute found in our homepage Route:

<Route exact path="/" component={Homepage} />

To give you an idea of how critical the attribute is, try deleting “exact,” save, and try visiting each of the three paths in your browser. What happens?

You should end up seeing something like this:

All is well here, but if we go to /cat…

Wait a minute… the homepage and the cat component are showing up?

Make it stop!!!

Much like an if-else statement made up of only ifs, the Router is going down your list of routes and seeing what matches. In case of ‘/dog’, it includes ‘/’ so it renders the Homepage component and the Dog component. For ‘/cat’, the same thing happens, except with the Cat component.

‘exact’ fixes this problem, as it ensures we are only rendering a component if it matches exactly.


‘Not Found’ 404 Component

Following the logic of how Route decides which routes to display, can you guess how we can make a “page not found” component render any time an unrecognized pathname is accessed?

We add this to our components:

const NotFound = () => {
  return "Page not found."
}
…
export { … NotFound }

And a final Route in routes.js, with no path attribute:

<Route component={NotFound} />

Keep it at the bottom, so it’s only reached when none of our other pathnames match the user’s request.

But this isn’t enough! The most important part is importing the Switch route from ‘react-router-dom.’ Switch enables us to only render one component at a time. If we don’t use Switch, our 404 route always renders at the bottom of our pages.

If your routes.js page is ready and looks something like this…

import React from "react"
import { Route, Switch } from "react-router-dom"
import { Homepage, Dog, Cat, NotFound } from "./components"

const Routes = () => {
  return (
    <div>
      <Switch>
        <Route exact path="/" component={Homepage} />
        <Route path="/dog" component={Dog} />
        <Route path="/cat" component={Cat} />
        <Route component={NotFound} />
      </Switch>
    </div>
  )
}

export default Routes

…try inputting a nonsense pathname to see your 404 page appear.

Note that Switch is not a substitute for exact. In fact, if you delete ‘exact’ from your Homepage route now, your /dog and /cat routes won’t show up when you visit them! We simply stay on the first route whose pathname most immediately matches the user’s input — for /dog and /cat, the buck stops at ‘/’.


Links in React Router

Now, finally, let’s write links in our components.js file.

In components.js or in your Homepage component file, Import { Link } from ‘react-router-dom’, and write a link with this format to link to dog and cat:

<Link to="/pathname">Click here</Link>

Your Homepage might now look like this:

const Homepage = () => {
  return (
    <div>
      <p>Welcome to my website about my pets.</p>
      <ul>
        <li>
          <Link to="/dog">My Dog</Link>
        </li>
        <li>
          <Link to="/cat">My Cat</Link>
        </li>
      </ul>
    </div>
  )
}

Navigation

We don’t need to depend on the browser’s navigation buttons to return to the homepage; we can make our own back and forward buttons for our Dog and Cat components.

You can imagine the history object as an array containing indexes that indicate where we are in terms of ‘back’ and ‘forward.’ Someone who has been clicking around without hitting ‘back’ is at the end of the array, but when they hit back once, they are now at the second-to-last index. Pretty straightforward, right?

A button that takes us back a step, regardless of where we came from, looks like this:

const Dog = props => {
  return (
    <div>
      <p>
        My dog Thundric is great. I couldn’t decide between Thunder or Electric. Don’t call him Rick.
      </p>
      <p>
        <button onClick={() => props.history.goBack()}>Back</button>
      </p>
    </div>
  )
}

A forward button would look like this:

<button onClick={() => props.history.goForward()}>Forward</button>

And one that goes N steps forward or backward like this:

<button onClick={() => props.history.go(-2)}>Back two pages</button>

We can even add a home button that pushes Homepage into our history ‘array’, and takes us there:

<button onClick={() => props.history.push("/")}>Home</button>


Rendering several components at once (Nav bar style)

You may not want your entire webpage to transform with every click, so don’t overlook this way of writing your routes. Try it yourself:

const Routes = () => {
  return (
    <div>
      <p>Hi there! This text stays put, and the other components rotate below.</p>
      <Switch>
        <Route exact path="/" component={Homepage} />
        <Route path="/dog" component={Dog} />
        <Route path="/cat" component={Cat} />
        <Route component={NotFound} />
      </Switch>
    </div>
  )
}

Finding space outside of Switch routes is ideal for a navigation bar; it stays in one place as the user clicks through routed components.

const Routes = () => {
 return (
   <div>
     <Link to="/">Homepage</Link> {/*...etc*/}
     <Switch>
       <Route exact path="/" component={Homepage} />
       <Route path="/dog" component={Dog} />
       <Route path="/cat" component={Cat} />
       <Route component={NotFound} />
     </Switch>
   </div>
 )
}

Nested Routes

Topic change: you put your pets’ website aside, and made a new one about your marbles collection.

If you would like to feature each of your 1,000 marbles on your website individually, it makes sense to create just one component to reuse for each.

But how can React Router help us out here?

This would be a nightmare:

const Routes = () => {
  return (
    <Switch>
      <Route path="/marble/1" component={Marble} />
      <Route path="/marble/2" component={Marble} />
      <Route path="/marble/3" component={Marble} />
      ...
    </Switch>
  )
}

Instead we can use the params feature:

const Routes = () => {
  return (
    <Switch>
      <Route path="/marble/:id" component={Marble} />
      <Route component={NotFound} />
    </Switch>
  )
}

Amazing! So, how does the component know which marble to display?

Let’s look at the ready-to-go Marble component.

const Marble = props => {
 return <div>This is Marble number {props.match.params.id}!</div>
}

‘props.match.params’ accesses the ‘params’ (the bit after the colon) we set in our Routes. Depending on what pathname is accessed by the user, we will go to a Marble component tailored to the ID in the URL.

So if a user clicks a link that takes them here:

localhost:3000**/marble/241**

The component will render: “This is Marble number 241!”

Below is a more fleshed-out Marble component, which not only displays the ID to the user, but pulls the correct marble from the database using the ID given in the URL. That’s a much more dynamic use of params; the user probably cares about a lot more than just the ID!

class Marble extends Component {
  constructor() {
    super()
    this.state= {
      color: "",
      millimeters: 0
    }
  }

  async componentDidMount() {
    try {
      const res = await axios.get(`/marble/${this.props.match.params.id}`)
      const uniqueMarble = res.data
      this.setState({
        color: uniqueMarble.color,
        millimeters: uniqueMarble.millimeters
      })
    } catch (err) {
      console.error(err)
    }
  }

  render() {
    return <div> This is Marble number {this.props.match.params.id}!
                 It's the color {this.state.color} and only {this.state.millimeters} millimeters small.</div>
  }
}

In summary

Here is what we covered:

And that is React Router! Share this article to show your network how much you know.


Related Posts

Learn React.js for Beginners

React Hooks Tutorial for Beginners: Getting Started With React Hooks

Learn React - React Crash Course 2019 - React Tutorial with Examples

Modern React with Redux

The Complete React Web Developer Course (2nd Edition)

Node with React: Fullstack Web Development

Beginner Full Stack Web Development: HTML, CSS, React & Node

React JS and Redux - Mastering Web Apps

React 16 - The Complete Guide (incl. React Router 4 & Redux)

MERN Stack Front To Back: Full Stack React, Redux & Node.js

Originally published by Francisco Huergo at https://programmingwithmosh.com