320H.10 - React Router
Learning Objectives
By the end of this lesson, learners will be able to:
- Setup and use React Router.
- Create Router, Route, Link, and Switch components.
- Pass Router props.
- Use URL parameters.
- Consume a third-party API in React.
The Problem
We are often used to making websites with several "pages" which would be split across several HTML files delivered statically or rendered by templates on a server. When making a React app, the application is a single page with one HTML file. We can have components conditionally render to create the illusion of pages, but it doesn't quite feel as intuitive as using <a>
tags to link to different HTML files.
What the React-Router library does is allow us to define components that render based on the URL in the address bar. We link to them with <Link>
components, which feel similar to the <a>
tags that we are accustomed to. This allows us to create a single page application in a way that feels like a multi-page application.
We will explore all of the tools that make this possible in detail in a guided lab activity at the end of this lesson, which provides a brief introduction to the core concepts.
Using BrowserRouter
The key to using routes within React lies within BrowserRouter
from react-router-dom
. We import BrowserRouter
into our index file, which then provides context and tools to all of its child components. In order to provide those tools to the entire application, we wrap our App
component within it.
As an example of a typical index.js
file:
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import "./style.css";
import App from "./App";
// Import BrowserRouter
import { BrowserRouter } from "react-router-dom";
// Wrap the App component with the BrowserRouter component to enable the router features.
ReactDOM.render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>,
document.getElementById("root")
);
Creating Routes
The two components that allow us to create routes within our application are Route
and Routes
from react-router-dom
.
Routes
is a wrapper component that contains children Route
components, like you would see in an HTML list with <ul>
and <li>
.
Route
components have two attributes that must be set in order to create working routes:
- The
path
attribute specifies what URL the route is looking for (e.g.path="/profile"
). - The
element
attribute tells the route which page component to render when the path is reached (e.g.element={<Profile />}
)
import "./App.css";
// Import Route and our components
import { Route, Routes } from "react-router-dom";
import Main from "./pages/main";
import Profile from "./pages/profile";
export default function App () {
return (
<div className="App">
<Routes>
<Route path="/" element={<Main />}/>
<Route path="/profile" element={<Profile />}/>
</Routes>
</div>
);
}
Navigation
In order to navigate between these paths, react-router-dom
provides another component called Link
. Link
components have a to
attribute that specifies what path they link to. Whatever element the Link
component is wrapped around becomes a navigation link to the given path. The function of the <Link>
tags is to change the URL bar to match the to
prop.
The reason we don't use an <a>
tag is because clicking an <a>
tag triggers the browser to make a request and refresh the page, which will break our React application (because there is no server to respond to the browser's request; the URL is merely a simulation of multiple pages).
import { Link } from "react-router-dom";
export default function Nav (props) {
return (
<div className="nav">
<Link to="/">
<div>Main</div>
</Link>
<Link to="/profile">
<div>Profile</div>
</Link>
</div>
);
}
URL Parameters and the useParams Hook
Route
components also accept URL parameters, variables within the URL, as parts of their path. Using the following syntax, whatever is in the spot follow the :
will be accessible by the useParams
hook.
<Route path="/profile/:id" element={<Profile />}/>
The :symbol
part is a URL param - a variable in the URL. Whatever is in that spot in the path will be accessible by using the useParams
hook, which we will look at shortly.
The useParams
hook is quite simple to use. By declaring a variable, in this case params
, and setting it to useParams()
, we can access parts of the URL specified by the syntax of our Route path
, as follows:
import {useParams} from "react-router-dom"
export default function Profile (props) {
// Grabbing the id from the URL Params.
const params = useParams()
const UID = params.id
// Some logic to render a specific user's profile by ID...
}
Third-Party APIs
Accessing information from other web applications is a key component of web development. In React, we can consume (make use of) third-party APIs by using three common JavaScript tools: async/await
, try...catch
, and fetch
.
If you are unfamiliar with any of these concepts, take a look at the documentation for them:
In React, our typical pattern for using these tools is:
-
Create an
async getData()
function tofetch
from an external API URL.await
the response from thefetch
request.- Enclose the request in a
try...catch
block to handle errors.
- Use
useEffect
to run the API call once when the component loads. -
Handle the two possible states - having data or not having data - with two different functions:
loaded()
, which displays the returned data if it exists, andloading()
, which displays come loading text (or commonly a spinner component) when data does not exist.
- Check for data, and return
loaded
orloading
as appropriate.
Here's an example of the above logic:
import {useState, useEffect} from "react";
import {useParams} from "react-router-dom"
export default function Component (props) {
// Our api key from some third-party API.
const apiKey = "YOUR API KEY";
const url = `http://some.api.com/api/getData?apikey=${apiKey}`;
// State to hold the data.
const [data, setData] = useState("null");
// Function to fetch data.
const getData = async () => {
try {
const response = await fetch(url);
const data = await response.json();
setData(data);
} catch(e) {
console.error(e)
}
};
// useEffect to run getData when component mounts.
useEffect(() => {
getData();
}, []);
// loaded function for when data is fetched.
const loaded = () => {
return (
<div>
<h1>{data.somefield}</h1>
<h2>{data.someOtherField}</h2>
</div>
);
};
// Function for when data doesn't exist.
const loading = () => {
return <h1>Loading...</h1>;
};
// If data exists, run the loaded function; otherwise, run loading.
return data ? loaded() : loading();
}
Now, what if you had many different components that all wanted to getData()
? Or, more than likely, you're working with an API that has many different options available, such as a URL for signing up to a service, one for signing in, one for signing out, one for getting profile information, one for getting purchase data... You get the idea. More often than not, you'll want to organize your API calls into a service module. This service module can then be imported into components that require it, and its functions reused.
Getting Started with React Router
The best way to solidify your understanding of React Router is to put it to use.
Complete this guided lab activity: GLAB 320H.10.1 - React Router App.
This lab activity will guide you through building a simple "multi-page" application using React Router. Key information from this lesson has been included within the lab for quick reference.
If you run into issues while completing the lab, speak to your instructors.
Consuming Third-Party APIs
Once you've completed the above guided lab, complete the following assignment lab.
R-ALAB 320H.10.1 - Consuming Third-Party APIs will test your ability to create a React app that populates its data from a third-party API.