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>
  );
}

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 to fetch from an external API URL.

    • await the response from the fetch 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, and
    • loading(), which displays come loading text (or commonly a spinner component) when data does not exist.
  • Check for data, and return loaded or loading 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.

Copyright © Per Scholas 2024