Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

A look at React Router 6

July 15, 2020
react

The release of React router version 6 is just around the corner. I thought it was time to take a good look at it …

Installing

To use React Router, we need to install the react-router-dom and history packages.

npm install history react-router-dom@next

Note that react-router-dom is still in beta, so we’ve explicitly defined the next version.

Setting up top-level routes

A route represents a page in an app. We define all the routes using a number of elements in JSX from React Router:

<BrowserRouter>
  <Routes>
    <Route path="" element={<HomePage />} />
    <Route
      path="customers"
      element={<CustomersPage />}
    />
    <Route
      path="products"
      element={<ProductsPage />}
    />
    <Route
      path="products/:id"
      element={<ProductsPage />}
    />
  </Routes>
</BrowserRouter>

The BrowserRouter element is usually the topmost element in the component tree because all other React Router elements need to be nested within it. BrowserRouter provides information about the current location to its descendants and will perform navigation between pages.

A set of routes is defined as a Routes element. Each route is defined in a Route element within it. Whenever the location changes, Routes finds the child Route element that best matches the path prop to the current location and renders the element defined in the element prop. So, the Route element is a bit like an if statement - if its path matches the current path, it renders its element.

Routes replaces Switch in previous versions of React Router and a key difference is that the ordering of the Route elements within it don’t impact the matching like it used to do with Switch. The matching in Routes is much smarter with precedence based on how specific a the path is with a Route.

Navigation

Links

A Link element will navigate to the route specified in the to prop when clicked.

<Link to="buy">Buy</Link>

to is a relative path to the route that rendered it if the path doesn’t start with a /. If the path begins with a /, it is relative to the root of the app.

Link is an a element under the hood but doesn’t cause server-side navigation - the navigation happens all in the browser.

There is another element that we can use for linking called NavLink. Unlike Link, NavLink knows whether or not it is active:

<nav>
  <NavLink to="/" activeClassName="active" end>
    Home
  </NavLink>
  <NavLink
    to="customers"
    activeClassName="active"
  >
    Customers
  </NavLink>
  <NavLink
    to="products"
    activeClassName="active"
  >
    Products
  </NavLink>
</nav>

An activeClassName or activeStyle prop can be used to style the underlying a element when its route is active.

The end prop means that NavLink will only become active if the current path is a full match. In the example above, without end, Home will always be active.

Programmatic navigation

Programmatic navigation can be achieved with the useNavigate hook. The hook returns a function that will invoke navigation with the path passed into it:

const navigate = useNavigate();

A common use case is a form submission:

<form
  onSubmit={(e) => {
    e.preventDefault();
    const formData = new FormData(
      e.currentTarget
    );
    // submit form
    navigate("/success");
  }}
>
  <input
    name="name"
    type="text"
    placeholder="name"
  />
  <button type="submit">Save</button>
</form>

The useNavigate hook is new in version 6 and is suspense ready.

Not found routes

A not found route can be defined when no other route matches the current path:

<Route path="/*" element={<NotFoundPage />} />

Path /* is used because it has the weakest precedence, which means the router will only pick it up if no other routes match.

Route parameters

Parameters can be added to a path after a colon:

<Route
  path="products/:id"
  element={<ProductPage />}
/>

The useParams hook can then be used to retrieve a parameter value from the path:

const { id } = useParams();
const product = products.find(
  (p) => p.id === id
);

Descendant routes

You can have more than one set of routes with the Routes element. Further down the component tree, within the top-level Routes element, we can have an additional Routes element:

<Routes>
  <Route
    path="buy"
    element={
      <p>Thank you for buying this product!</p>
    }
  />
  <Route
    path="*"
    element={
      <Link to="buy" className="link">
        Buy
      </Link>
    }
  />
</Routes>

This is within ProductPage within the following route:

<Route
  path="products/:id*"
  element={<ProductPage />}
/>

The 2nd level routes result in a Buy button initially being rendered. When this is clicked, it is replaced with the message, Thank you for buying this product!.

Nice!

Nested layout

Route elements can be nested inside one another:

<Route
  path="products"
  element={<ProductsPage />}
>
  <Route path=":id*" element={<ProductPage />} />
</Route>

In the above example, ProductsPage will be rendered for the products path. Both ProductsPage and ProductPage will be rendered when the path is products/:id.. An Outlet element is used to specify where ProductPage is rendered within ProductsPage.

<div className="product-list">
  {filteredProducts.map(({ id, name }) => (
    <Link key={id} to={`/products/${id}`}>
      {name}
    </Link>
  ))}
</div>

<Outlet />

This approach can produce nested layouts where the surrounding UI remains consistent while the inner content changes between routes.

Search parameters

The useSearchParams is used to read and update search parameters. It returns an array of two values: the current location’s search params and a function to update them:

let [
  searchParams,
  setSearchParams,
] = useSearchParams();

searchParams is a set utility methods allow specific search parameters to be retrieved.

setSearchParams can be used to update the search parameters:

<form
  onSubmit={(e) => {
    e.preventDefault();
    setSearchParams(
      `search=${new FormData(
        e.currentTarget
      ).get("search")}`
    );
  }}
>
  <input
    name="search"
    type="search"
    placeholder="search ..."
  />
</form>

A working example of these features is available in CodeSandbox at https://codesandbox.io/s/react-router-6-0y5qc?file=/src/App.tsx.

Wrap up

The new Routes component in version 6 is a great addition to React Router. Its relative matching makes code a little cleaner. The ability to do nested layouts with nested Route elements and Outlet is a nice touch. Well worth checking out!

Did you find this post useful?

Let me know by sharing it on Twitter.
Click here to share this post on Twitter

If you to learn about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Find out more