×
By the end of this chapter, you should be able to:
react-router
to build navigation for single page applicationsSo far all of our React applications have been composed of a few components. As our applications grow, we will need to change the components on the page. Often the changes happen because of a link that was clicked or because of data that has been submitted.
Any client-side (JavaScript) application that transitions between pages without refreshing the page or rendering a new HTML file from the server is called a Single-Page Application. On a well-designed SPA, the user should feel like the page still functions just like a standard server-based web app.
Specifically:
You've already been utilizing SPA concepts already! By adding e.preventDefault()
to your React forms, you are preventing the old-school behavior of HTML forms, which traditionally want to change pages on submission. In a SPA on the other hand, we keep the user on the same page and update the views dynamically with JavaScript and AJAX.
So if JavaScript is handling all of the changes to the page, how can the browser back button still work? The answer is the HTML 5 history
API. MDN has a good intro to history
. Assume you are on a site with the following url: https://www.example.com
. You can change the browser history with JavaScript:
// We can store some data we care about here var state = { data: "value" }; // browser url will change to https://www.example.com/yoyo.html window.history.pushState(state, "title not used right now", "yoyo.html"); // from javascript you can simulate clicking the browser back button // The url in the browser will change back to https://www.example.com/ window.history.back();
If you want to build a SPA in JavaScript and include routing using the browser's back button, you will need to utilize the history
API. However, you will rarely interface with this API directly; typically you'll be using some other tool that abstracts away any direct interaction with the history
API. In the case of React, the tool we use to enable routing is called react-router
.
Some frameworks like Angular and Ember come with a router, but since React is a much smaller library, it does not have its own router. To use client side routing with React, we're going to install a popular router called react-router
. React Router has three separate packages:
react-router
(base dependency package, not installed directly)react-router-dom
(this package is used for web development)react-router-native
(this package is used for mobile development).Note: We won't be installing react-router
directly. It will be installed as a dependency of react-router-dom
.
So let's get started with react-router
! Let's first create an application using create-react-app
then add react-router-dom
to it. In your terminal type the following:
create-react-app react-router-demo && cd react-router-demo npm install --save react-router-dom
First, go to to the src/index.js
file inside of your demo project. We are going to wrap the App
component with a router. The code should look like this:
import React from "react"; import ReactDOM from "react-dom"; import App from "./App"; import { BrowserRouter } from "react-router-dom"; import registerServiceWorker from "./registerServiceWorker"; import "./index.css"; ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById("root") ); registerServiceWorker();
There are a few things to take note of here. First, we imported BrowserRouter
. The BrowserRouter
, as the name implies, is a router designed for building single page applications in the browser. We'll highlight other types of routers momentarily, but in this course, BrowserRouter
is typically the one you'll need.
Second, notice that our App
component is now a child of BrowserRouter
. It is important to take note that the router component we import from React Router expects exactly one child. Therefore whenever you use React Router, you will need to wrap your application in a router just like in the example above.
HashRouter
vs BrowserRouter
There are a few router components that come with the React Router. Here is brief description on each one:
Router
- This is the fundamental router component that React Router uses. You won't really be importing this directly, but higher-level routers depend on it.
Here's a list of the routers you're more likely to be working with:
BrowserRouter
- This router component uses the HTML5 history
API, and is the most commonly used high-level router when building SPAs for the browser with React and React Router.
HashRouter
- This router is designed for support with older browsers that may not have access to the history
API. In such cases, you can still get single-page type functionality by inserting an anchor (#
) into the URL. However, this does not provide full backwards-compatibility: for this reason, the React Router documentation recommends BrowserRouter
over HashRouter
if possible.
To see the most obvious difference between HashRouter
and BrowserRouter
, let's change up our existing app so that import HashRouter
in our index.js
instead of BrowserRouter
. When you use this router instead, you'll see that your URLs all have a hashtag (#
) separating the host from the path. As we mentioned above, this can be useful as a fallback for older browsers that don't support the history API. For more on this, check out this article on the HTML5 history API.
NativeRouter
- This router is designed for React Native applications.
MemoryRouter
- This router mocks the history
API by keeping a log of the browser history in memory. This can be helpful when writing tests, since tests are typically run outside of a browser environment.
StaticRouter
- This is a router that never changes location. When would you ever use this? According to the docs, "This can be useful in server-side rendering scenarios when the user isn’t actually clicking around, so the location never actually changes. Hence, the name: static. It’s also useful in simple tests when you just need to plug in a location and make assertions on the render output."
BrowserRouter
fallbackIn order to use BrowserRouter, we need to specify a fallback - that is, what route to go to when a full refresh comes in (i.e. typing something in the browser bar and hitting enter). This can be done in the webpack.config.js
and is handled for us when using create-react-app
- you can read more about it here.
You won't need to worry about this when you're working locally, but may be an issue in production. The first time you deploy, you may also need to configure your remote server with a static.json
file that tells the server how to handle requests to routes other than the root. Here's what that file would look like:
{ "root": "build/", "routes": { "/**": "index.html" } }
This tells your server to make the build
directory your root, and to respond with the index.html
file for every request. Without this file, if you deployed our example app and tried to go to /name/matt
, the server would return a 404, since it doesn't know how to respond to this request! This is a route that only React Router understands.
By telling your server to respond to all requests with index.html
, any requests made by manually entering an address in the URL bar are processed as follows:
index.html
, which includes your bundled JavaScript files;Our first React Router app is pretty boring so far. Let's makew things more interesting by adding some routes.
To do this, edit src/App.js
to have the following structure.
import React from "react"; import "./App.css"; import { Route, Link } from "react-router-dom"; const Home = () => ( <div> <h1>I'M HUNGRY</h1> <iframe src="https://giphy.com/embed/pBj0EoGSYjGms" width="480" height="288" /> </div> ); const Eat = () => ( <div> <h1>NOM NOM NOM</h1> <iframe src="https://giphy.com/embed/VvQvOFqPjZLi" width="480" height="480" /> <p>So tasty!</p> </div> ); const Drink = () => ( <div> <h1>SO REFRESHING</h1> <iframe src="https://giphy.com/embed/DbD6EnlEQmjTi" width="480" height="461" /> </div> ); const App = () => ( <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/eat">Eat</Link> </li> <li> <Link to="/drink">Drink</Link> </li> </ul> <Route exact path="/" component={Home} /> <Route path="/eat" component={Eat} /> <Route path="/drink" component={Drink} /> </div> ); export default App;
As you may have noticed, this example uses two new components from React Router:
Route - this component basically acts as a translation service between routes and components. You tell it what path to look for in the URL, and what component it should render when it detects a match to that path. We can give this component quite a few more props aside from path
, and component
- we will see more later.
Notice that the Route
for path="/"
has the exact attribute. That tells react router that you only want to match the route exactly. Without the exact attribute, the content of the Home
component will always show up, since /
matches /
, /eat
, and /drink
.
Link - this component is used as a replacement for the built-in anchor tag. If we're building a single-page application, anchor tags can't really be used, since they cause the page to reload. So when you're using React Router, you should be using <Link>
instead of <a>
to control links on the page.
Try running the application and clicking on the links. Notice that the address bar in the browser is changing. If you open up your network tab in chrome developer tools and check for HTTP requests, you should see that nothing is happening on the network and that the browser is not reloading the page.
So how does this work? Whenever you click on a Link
component, react uses window.history
to change the url in the address bar. The Route
component renders the component specified in the component attribute whenever the current url path matches the path attribute.
In our example above, none of the components we wrote (Home
, Eat
, Drink
, App
) have any props. However, when you pass a component to Route
, the component you pass automatically gets access to three props: match
, location
, and history
. These props provide you with information about the route, the query string, and give you the ability to mutate the history. We'll examine these props in more detail as we go. For now, you can check out the docs on route props here. You can see these props more explicitly by using the React tab in the Chrome dev tools.
Alternatively, you can also display the props on the component directly. For example, suppose you modify the Eat
component as follows:
const Eat = props => ( <div> <h1>NOM NOM NOM</h1> <pre>{JSON.stringify(props, null, 4)}</pre> <iframe src="https://giphy.com/embed/VvQvOFqPjZLi" width="480" height="480" /> <p>So tasty!</p> </div> );
Now when you go to /eat
, you should be able to see all of the props on the page.
Once common use case for the route props is working with URL parameters. Just like we saw with server-side programming, we can design routes with dynamic URL parameters. In order to access these parameters, we use the match
object given to us by React Router. More specifically, the any URL parameters will live inside of the match.params
object as key-value pairs. We can access values in the query string using the location
object given to us by React Router as well.
Let's see what that looks like. First, create a new file called src/ParamsExample.js
. Inside of src/App.js
, add a new link for the new component we will build:
import React from "react"; import "./App.css"; import { Route, Link } from "react-router-dom"; import ParamsExample from "./ParamsExample"; const Home = () => ( <div> <h1>I'M HUNGRY</h1> <iframe src="https://giphy.com/embed/pBj0EoGSYjGms" width="480" height="288" /> </div> ); const Eat = () => ( <div> <h1>NOM NOM NOM</h1> <iframe src="https://giphy.com/embed/VvQvOFqPjZLi" width="480" height="480" /> <p>So tasty!</p> </div> ); const Drink = () => ( <div> <h1>SO REFRESHING</h1> <iframe src="https://giphy.com/embed/DbD6EnlEQmjTi" width="480" height="461" /> </div> ); const App = () => ( <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/eat">Eat</Link> </li> <li> <Link to="/drink">Drink</Link> </li> <li> <Link to="/name">Instructors</Link> </li> </ul> <Route exact path="/" component={Home} /> <Route exact path="/eat" component={Eat} /> <Route path="/drink" component={Drink} /> <Route path="/name" component={ParamsExample} /> </div> ); export default App;
Here we have added a Link
to a fourth page, whose content is determined by ParamsExample
. More specifically, we will render the ParamsExample
for any path that starts with /name
.
Next, in the src/ParamsExample.js
file, add the following:
import React from "react"; import { Route, Link } from "react-router-dom"; const Instructor = ({ match, location }) => { const { name } = match.params; return ( <div> <h3>Instructor Info For {name ? name : "No One"}</h3> <h4> What's in match? <pre>{JSON.stringify(match, null, 4)}</pre> </h4> <h4> What's in location? <pre>{JSON.stringify(location, null, 4)}</pre> </h4> </div> ); }; const ParamsExample = () => ( <div> <h2>Instructors:</h2> <ul> <li> <Link to="/name/elie">Elie</Link> </li> <li> <Link to="/name/matt">Matt</Link> </li> <li> <Link to="/name/tim">Tim</Link> </li> </ul> <Route path="/name/:name" component={Instructor} /> </div> ); export default ParamsExample;
In src/ParamsExample.js
, we have 2 components: ParmsExample
and Instructor
. The route renders an instructor component based the on the current path. In the instructor component, you can see that we're rendering a few special props: match
and location
. When we pass a component into Route
using the component
prop, that component will render with match
and location
props that expose information about the current path, including any URL parameters.
React Router does not ship with a built in way to parse a query string. The recommended action is to use the URLSearchParams
constructor in the browser. Here is what that might look like:
let url = new URLSearchParams("https://infschool.com? instructor=Michael&admissions=Angelina&favorite_dog=Whiskey") url.get("admissions") // "Angelina"
You can see more about this here, and read about URLSearchParams here.
So far we have seen how to route to other components, and that the components we route to will have access to the router's props. But what if we want our components to have access to other props as well, not just those coming from the router?
There are a couple of ways to do this. One way us to create a stateless functional component on the fly inside of the component
prop for Route
. Let's see what that looks like:
import React from "react"; import "./App.css"; import { Route, Link } from "react-router-dom"; const Home = props => ( <div> <h2>Hello {props.name}! </h2> </div> ); const About = () => ( <div> <h2>About</h2> </div> ); const App = () => ( <div> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> </ul> <Route exact path="/" component={props => <Home name="Elie" {...props} />} /> <Route path="/about" component={About} /> </div> ); export default App;
Notice here that we are passing a function as the value of the component prop. Since all of our components are functions anyway, we simply wrap our <Home />
component with a function! This is exactly the idea around higher order components. We're not explicitly creating a higher order component, but we are wrapping our component with a function that passes down props to the <Home />
component.
Complete the react router Todo application
When you're ready, move on to React Router Continued