By Alex Merced Updates by Ira Herman
- Use React Router's
BrowserRouter
,Link
,Route
,Redirect
, andSwitch
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
Install the following tool(s):
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 Router
Let's get set up with the react bitcoin price checker!
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!
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
asRouter
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 aRoute
.
Route - a component that renders a specified component (using either
render
orcomponent
) 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.
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.
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.
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} />
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 byRouter
and apply each of its key/value pairs as props on thePrice
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.
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>
);
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.
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 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 fromreact-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.
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.
- 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 |
-
Set up
react-router
like we did in this lesson, at the top level. What is the top level of a React app? -
Build out each component with placeholders to render something. Do this first, before starting to add state, props, or functionality.
-
Set up your routes so that each route only displays the appropriate component.
-
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.
-
Initialize your state and pass it down to the appropriate components.
-
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.
-
Marvel at your creation and your progress after only 7 weeks of programming!