Skip to content

Instantly share code, notes, and snippets.

@iscott
Last active December 7, 2020 22:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iscott/698c53b1fe67031880e645a7f42478af to your computer and use it in GitHub Desktop.
Save iscott/698c53b1fe67031880e645a7f42478af to your computer and use it in GitHub Desktop.

React Router

By Alex Merced Updates by Ira Herman

React Router Logo

Learning Objectives

  • Use React Router's BrowserRouter, Link, Route, Redirect, and Switch components to add navigation to a React application
  • Review the React component lifecycle and use component methods to integrate with API calls
  • Talk about SPAs

Installs

Install the following tool(s):

Framing

Up to this point, our React applications have been limited in size, allowing us to use basic control flow in our components' render methods to determine what gets rendered to our users. However, as our React applications grow in size and scope, we need an easier and more robust way of rendering different components. Additionally, we will want the ability to set information in the url parameters to make it easier for users to identify where they are in the application.

React Router, while not the only, is the most commonly-used routing library for React. It is relatively straightforward to configure and integrates with the component architecture nicely (since it's just a collection of components).

We will configure it as the root component in a React application. Then we'll tell it to render other components within itself depending on the path in the url. This way we don't have to reload the entire page to swap out some data.

Don't confuse it with the express router! They do different things, though they both operate based on paths.

React Bitcoin Prices

***React Router

Let's get set up with the react bitcoin price checker!

React Router Setup

Currently, we are rendering just the App component, which renders the Home component. Let's bring in React Router and set it up to allow us to display multiple components.

When working with a new library, it's useful to have the documentation handy!

Importing Dependencies

First, we need to install react-router and react-router-dom as dependencies in package.json. Running npm install with arguments should automatically do this for us.

react-router react-router-dom

To configure our current application to use React Router, we need to modify the root rendering of our app in index.js. We need to import the Router component and place it as the root component of our application. Router will, in turn, render App through which all the rest of our components will be rendered:

// index.js
import { BrowserRouter as Router } from "react-router-dom";

//...

ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById("root")
);

Note that we are aliasing BrowserRouter as Router here for brevity.

By making Router the root component of our app, it will pass down several router-specific objects to its child components. Things like current location and url can be accessed or changed. Additionally, in order to use the other routing components provided by React Router, a Router parent component is necessary.

Next, in App.js, we need to import all of the other components we want to use from React Router.

The three main ones we're going to use today are:

<Route />
<Link />
<Switch />

Let's go ahead and import just route and link for now, we'll cover switch later.

// src/components/App/App.js

import { Route, Link } from "react-router-dom";

Now that we have access to these components, we need to modify the App component to set up navigation. The basic structure we will use is this:

// src/components/App/App.js

return (
  <div>
    <nav>
      // the link component produces an a element
      <Link to="/">
        <img src="https://en.bitcoin.it/w/images/en/2/29/BC_Logo_.png" alt="" />
        <h1>Bitcoin prices</h1>
      </Link>
    </nav>
    <main>
      // we can give either a render or a component prop.
      <Route path="" component={} />
    </main>
  </div>
);

Link - a component for setting the URL and providing navigation between different components in our app without triggering a page refresh. It takes a to property, which sets the URL to whatever path is defined within it. Link can also be used inside of any component that is connected to a Route.

Route - a component that renders a specified component (using either render or component) based on the current url (path) we're at. path should probably match a <Link to=""> defined somewhere.

Now let's modify the render method in App.js to include our Route component.

// src/components/App/App.js

return (
  <div>
    <nav>
      <Link to="/">
        <img src="https://en.bitcoin.it/w/images/en/2/29/BC_Logo_.png" alt="" />
        <h1>Bitcoin prices</h1>
      </Link>
    </nav>
    <main>
      <Route path="/" component={Home} />
    </main>
  </div>
);

Great! But this doesn't do anything because we're already on the homepage.

Also, note that we used component in this case to display our home component. We're doing that because we just want to display it without any changes - we're not passing any props in, we're not modifying anything.

You do: Add a Second Route and Link (10 min / 0:50)

5 minute exercise / 5 minute review

  • Using the above instructions as a guide, add a new Link to /currencies and a route to match it. What component do you think you want to render?
Solution
// src/Components/App/App.js
//...
import Currencies from "../Currencies/Currencies";

// ...

return (
  <div>
    <nav>
      <Link to="/">
        <img src="https://en.bitcoin.it/w/images/en/2/29/BC_Logo_.png" alt="" />
        <h1>Bitcoin prices</h1>
      </Link>
      <Link to="/currencies">Currency List</Link>
    </nav>
    <main>
      <Route path="/" component={Home} />
      <Route path="/currencies" component={Currencies} />
    </main>
  </div>
);

Now we've got two components and two routes. Perfect. Let's take a look at our currencies component and see what we need to do to make it work.

This a good point to talk about React Router's Route Props.

Currencies component

If we look at this component we see a long list of links. Note that the links are using regular <a> tags.

What happens if we click on a link? It works, but the whole page reloads! Gross. Let's fix that.

Go ahead and replace the a tag with a <Link> component. Make the to prop value equal to the href value.

// src/Components/Currencies/Currencies.js
import { Link } from 'react-router-dom'

//...

    let list = listOfCurrencies.map(item => {
      return (
        <div className="currency" key={item.currency}>
          <p><Link to={"/price/"+ item.currency}>{item.currency}</Link>: {item.country}</p>
        </div>
      )
    })

  // ...

Great! Now go back to the page and click the link again, what happens?

It changes the route for us (notice the URL changing) but we don't have any routes set up to match that. Let's do that next.

Prices Component and Params

Back in App.js, we need to add another <Route> component. This time though, we want to include a parameter.

Look at the URL that we're on after clicking on a currency. Then look at the Price component. How might you write the path prop to make it work?

The answer is to include a url parameter. This is done by appending a :paramName after the path.

<Route path="/price/:currency" component={Price} />

Fix prices component

We've added a route but not everything will work yet. HOW COME!?

There are a couple things we need to fix.

Firstly, we have to make sure we're using render instead of component in our route. That's because we're going to be passing some props into our component and component will only allow us to render a component but not pass it any props.

//...
<Route path="/price/:currency" render={() => <Price />} />
//...

This will cause our app to error out at this point so we need to add a couple things to <Price />

We also have to pass our URL parameters into <Price />. This is where the arrow function comes in to play.

<Route
  path="/price/:currency"
  render={(routerProps) => <Price {...routerProps} />}
/>

The ... (spread operator) is allowing us to "destructure" the props object being passed by Router and apply each of its key/value pairs as props on the Price component.

We've got a function in this (App.js) component called handleClick. let's pass that in aw well

<Route
  path="/price/:currency"
  render={(routerProps) => <Price handleClick={handleClick} {...routerProps} />}
/>

Finally, we need to pass in the current component's state.

<Route
  path="/price/:currency"
  render={(routerProps) => (
    <Price handleClick={handleClick} {...routerProps} price={price} />
  )}
/>

Now let's apply this to the router!

We know what our state looks like, so we can use that as an example.

const [price, setPrice] = useState(null);

This turns into:

<Price price={price} />

If we use the react dev tools, we can see what props have been passed down from the routerProps object.

let routerProps = {
  history: {
    /* stuff in here */
  },
  location: {
    /* stuff in here */
  },
  match: {
    /* stuff in here */
  },
};

So if we spread the routerProps object, we'll get something like this:

<Price
  history={ /* stuff in here */ }
  location={ /* stuff in here */ }
  match={ /* stuff in here */ }
>

Putting it all together, using the spread operator turns this:

<Price handleClick={handleClick} {...routerProps} price={price} />

Into this:

<Price
  handleClick={handleClick}
  history={/* stuff in here */}
  location={/* stuff in here */}
  match={/* stuff in here */}
  price={price}
/>

We still have some weird display quirks, and for that, we'll use <Switch> to fix them.

Using Switch

Switch works just like the switch/case statements in javascript. We're comparing string values (in this case, routes) and executing conditions (rendering components) based on what matches turn out true.

Since we're not using switch right now the two components are stacked on top of each other! The Home and the Currencies component. That's silly.

Why does this happen?

There are two ways to handle this: using the Switch component, or specifying exact on routes.

Let's look at our routes in App.js again:

<Route path="/"
  component={Home}
/>
<Route path="/currencies"
  component={Currencies}
/>
<Route
  path="/price/:currency"
  render={(routerProps) => <Price handleClick={handleClick} {...routerProps} price={price} /> }
/>

Try putting exact on the / path route component.

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

Note: this is equivalent to putting exact=true

Beautiful! this is a great solution, unless we have many different routes.

If we had a list of routes like:

  • /currencies
  • /currencies/new
  • /currencies/:id etc

we would have to put exact on /currencies or else, any time we went to /currencies/something it would match both the root (/currencies) AND the /currencies/something routes and both would be rendered.

We can avoid all this by just using <Switch />.

Back in App.js, let's import the <Switch /> component and then wrap all of our routes in it.

import { Route, Link, Switch } from "react-router-dom";

return (
  <div>
    <nav>
      <Link to="/">
        <img src="https://en.bitcoin.it/w/images/en/2/29/BC_Logo_.png" alt="" />
        <h1>Bitcoin prices</h1>
      </Link>
      <Link to="/currencies">Currency List</Link>
    </nav>
    <main>
      <Switch>
        <Route path="/" exact component={Home} />
        <Route path="/currencies" component={Currencies} />
        <Route
          path="/price/:currency"
          render={(routerProps) => (
            <Price handleClick={handleClick} {...routerProps} price={price} />
          )}
        />
      </Switch>
    </main>
  </div>
);

You Do: The Coindesk API

We will be using fetch to query the Coindesk API in this exercise. Take 5 minutes to read the Coindesk API documentation and test viewing the current currency rate for a AED and using just the browswer. Do not try and make a fetch call yet.

Adding useEffect to Price

The Price component will be responsible for making the API call to coindesk to retrieve the current price of the curreny. In order to do that we will need to add useEffect and have it run when the component is first mounted.

useEffect(() => {
  const currency = props.match.params.currency;
  const url = `${coindeskURL}${currency}.json`;
  const makeApiCall = async () => {
    const res = await fetch(url);
    const json = await res.json();
    let newPrice = json.bpi[currency].rate;
    props.handleClick(newPrice);
  };
  makeApiCall();
}, []);

Redirects

Redirects using react router are incredibly easy. Redirect is just another component we can import and use by passing it a few props.

  • Import the Redirect component from react-router-dom
  • Add another route called /currency
  • Instead of rendering one of our components, put the Redirect component.
<Route path="/currency" render={() => <Redirect to="/currencies" />} />

Redirect only requires a to prop which tells it what path to redirect to.

You Do: Building New App and Using React Router

Here's a rough outline of how you should go about building react apps! Follow these suggestions, or don't, but they will probably help you a lot if you do them in order. I suggest reading through all of the steps before you start so you can become familiar with the big picture of the entire process.

  1. Start a new app. Call it user-router or something similar, it doesn't matter. You will be building four components (not including App.js). Each one will render something different:
Component Renders Route
Home "This is the homepage" /
Greet A greeting from a url parameter passed in /greet/:param
Users A list of users /users/
NewUser A form that lets you add a username /users/new
  1. Set up react-router like we did in this lesson, at the top level. What is the top level of a React app?

  2. Build out each component with placeholders to render something. Do this first, before starting to add state, props, or functionality.

  3. Set up your routes so that each route only displays the appropriate component.

  4. Plan out where you think your state should live. If you have to share state between multiple components, what's the best place to keep it? Think about what your state needs to contain.

  5. Initialize your state and pass it down to the appropriate components.

  6. Wire up those components to be able to display and update the state as necessary. Add the functionality to have the greet component receive and display a parameter.

  7. Marvel at your creation and your progress after only 7 weeks of programming!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment