DEV Community

Cover image for Client-side routing in React with React Location
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Client-side routing in React with React Location

Written by Tharaka Romesh✏️

Routing is one of the essential aspects of single-page applications (SPAs), but because React doesn't come with a routing solution, React developers must integrate third-party libraries to implement routing into their applications.

There are several routing libraries available, such as React Router, which is one of the most popular libraries used for server-side or client-side React applications. Even though React Router was the go-to library used for routing, there are several limitations to it, such as:

  • Search-based route matching
  • Route data caching
  • Route loader pre-fetching

To address these issues, React Query creator Tanner Linsley has developed a new routing library called React Location, which includes some neat features that make life easier for React developers.

What is React Location?

React Location is a powerful, enterprise-grade routing tool for React applications. React Location was built as a wrapper for the React Router v6 beta release that patched some limitations, but it has since evolved into a fully-fledged routing solution.

React Location offers asynchronous features that are not built into the other routing solutions:

  • Promise-based data loaders
  • Async route loaders
  • Threshold-based pending route elements
  • Post-render async loader APIs

React Location is inspired by full-stack React frameworks like Next.js and Remix, which come with built-in routing and offer a fresh take on concepts like data loader and search params.

Benefits of using React Location

React Location introduces some unique features that are incredibly helpful, which I’ll go into detail about below.

Data loaders

React Location offers a new method to execute asynchronous functions before rendering each route. You can use this method to fetch data using something like Axios, fetch, etc., from the server so the data will be available before rendering the component.

In a traditional application, you will need to fetch data after loading the route or rendering the component using a lifecycle method Hook like componentDidmount or useEffect. The data loaders will delay the loading of the route until the asynchronous functions execute.

React Location Devtools

Unlike React Router or any other React routing library, React Location provides Devtools, which enables you to visualize what happens behind the scenes of your application. In addition, this is quite helpful for debugging your application. To enable React Location Devtools, install the plugin and add the component to your React components tree.

Code splitting

Implementing code splitting with React Location is relatively easy. First, you need to pass a dynamic import of a React component as the element and define a loader for the component.

With react-router, you will need to install additional plugins like babel/plugin-syntax-dynamic-import and loadable-components or use the React experimental Suspense to achieve code splitting.

Search params

React Location unlocks the power of URL search params by enabling us to store states in the URL. React Location provides the ability to match, consume, and manipulate URL search params. As a result, the URLs are more shareable, can be bookmarked, and are more consistent.

Search params use the JavaScript JSON API (JSON.parse and JSON.stringify), which comes in handy when handling complex values and objects in the URL query parameters. React Location also allows you to use plugins like JSURL or custom methods (for stringifySearch and parse search API methods) to extend search param capabilities.

Even if you use a plugin or the default search param implementation, React Location will guarantee that search parameters will behave as an immutable object stored in your application's memory.

Nested routing

Defining nested routes is quite clear and easy to maintain.

Route Elements

After defining your routes and data loaders, you can render pages based on the state of your loader or routes. React Location allows you to handle or render your pages with:

  1. Element: this renders the default component that you need to render when your route is matched
  2. errorElement: this allows you to render a component when there is an error with your route or with your data loaders
  3. pendingElement: this allows you to render a component when your data loaders are in action, making it ideal for rendering loading pages or loaders. It also comes with more properties that allow you to define how long you need to render the pending state component

Exploring the React Location API

Let's dive into the React Location API. If you are familiar with React Router, you’ll find that most of React Location's APIs look quite similar.

Router is the the root provider component that behaves similar to the provider component in React Router. Link will behave similarly to React Router’s link API but has more functionalities built within it, such as the ability to update search parameters and hash. It also supports events like “Open in new tab” and “Open in new window” and has an active prop, which shows that link is in an active state.

Navigate will navigate to the route upon render, and useNavigate behaves similar to the useNavigate API method in React Router, enabling you to navigate your application programmatically.

Next,useRouter is a Hook used to access the parent component's state or the <Route>. It returns the current transition state of location or the next transition state of location.

useMatchRoute returns relative and absolute paths against the current or pending route. If the paths match, it will return an object with the route params. This is helpful when finding a specific deep-route match and if a particular route is the next pending location when navigation occurs. useMatch returns the nearest route that has been matched and is used to get route params, route data, and the next child match.

useSearch lets you access the search parameters and state of the application's current route. First, you will need to define the pieces of information you need to gather by declaring a JSON object via the MakeGenerics API method. Then, in your React component, you can use useSearch with JSON to gather the details. This generic object is immutable from render to render, so we can use it to detect changes in the search params.

useResolvePath provides a function that we can use to check the path (returns the name) relative to the route where you invoke this. With this Hook, you can retrieve the name of the current, previous, child, or parent’s route name.

Finally, you’ll probably need usePrompt for a page that has forms, as it allows you to programmatically prompt a dialog to the user, like when a user starts to route to another route before submitting the form.

Best tools and plugins for React Location

React Location provides you the flexibility to use several plugins so you can extend its features. Here are some ‌plugins that may come in handy for you when working with React Location.

  • React Location Simple Cache provides you with the functionality to cache the data loader values of your routes, so, if you move through the same routes, it will maintain a cache and reduce load time. This plugin also provides features to deal with the cache, like fetch policies and manual cache invalidation
  • React Location Devtools is one of the best and most unique features you can enable with this plugin, as it helps you visualize all the inner workings of React Location and makes it easy to debug the application
  • React Location JSURL is useful when you need to extend the search params features. It uses JSURL, an alternative to JSON and URL encoding (or JSON and base64 encoding), and with it, you can easily pass complex values via URL query parameters

Building an app with React Location

Let’s see how to use React Location by building a React application. We will use the REST Countries REST API. First, let's create an application using create-react-app through the following command:

npx create-react-app react-location-router --template typescript
Enter fullscreen mode Exit fullscreen mode

We will need several other libraries as well, including Material UI and Axios to build components fetch data:

yarn add axios @mui/material @mui/styled-engine-sc @emotion/react @emotion/styled

Let's create several components for our application: card, select, and navigation(HOC) components, and home, countries, country components. You can find the code example for them using the links provided. Now that we have several components, let's install React Location and configure the routes:

yarn add @tanstack/react-location
Enter fullscreen mode Exit fullscreen mode

Now it's time to define the routes in App.tsx.

import { Outlet, ReactLocation, Router } from "@tanstack/react-location";
import Home from "./pages/Home";
import Error from "./components/error/Error";
import Countries from "./pages/Countries";
import Country from "./pages/Country";
import Loader from "./components/loader/Loader";
import Navigation from "./components/navigation/Navigation";
import { getCountries, getCountry } from "./services";

// Set up a ReactLocation instance
const location = new ReactLocation();

function App() {
  return (
    <Router
      location={location}
      routes={[
        { path: "/", element: <Home /> },
        {
          path: "countries",
          element: <Countries />,
          loader: async () => {
              let { data } = await getCountries();
              return {
                countries: data,
              };
           },
          pendingElement: async () => <Loader />,
          pendingMs: 1000 * 2, // 2 seconds
          errorElement: <Error />,
          children: [
            { path: "/", element: <div>Select Country</div> },
            {
              path: ":code",
              element: <Country />,
              loader: async ({ params: { code } }) => {
                let { data } = await getCountry(code);
                return {
                  country: data,
                };
              },
              errorElement: <Error />,
            },
          ],
        },
      ]}
    >
      <Navigation>
        <Outlet /> {/* Start rendering router matches */}
      </Navigation>
    </Router>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

You can see the pending matches with the current matched paths, as well as the data provided through the data loaders. Now, let's access the data loader:

import { MakeGenerics } from "@tanstack/react-location";

export interface Country {
  name: {
    common: string;
    official: string;
  };
  borders: string[];
  population: number;
  cca2: string;
}

export type CountryGenerics = MakeGenerics<{
  LoaderData: {
    countries: Country[];
    country: Country;
  };
}>;
Enter fullscreen mode Exit fullscreen mode

To access the data, we will use the MakeGenerics API and define the JSON object to retrieve the data that we want. In this example, the data we will fetch is an array of countries and a single object of country details. Let's consume the data inside the components using useMatch.

import { useMatch } from "@tanstack/react-location";

// hook should be called inside the functional component
const { data } = useMatch<CountryGenerics>();
Enter fullscreen mode Exit fullscreen mode

We need to de-structure and access the properties to get the data:

 let { countries, country } = data;
Enter fullscreen mode Exit fullscreen mode

For ease of debugging, install the react-location-devtools plugin:

yarn add @tanstack/react-location-devtools
Enter fullscreen mode Exit fullscreen mode

Next, configure and import React Location Devtools inApp.tsx.

import { ReactLocationDevtools } from "@tanstack/react-location-devtools";
Enter fullscreen mode Exit fullscreen mode

Now add the ReactLocationDevtools component:

      <Navigation>
        <Outlet /> {/* Start rendering router matches */}
        <ReactLocationDevtools /> {/* enable Devtools */}
      </Navigation>
Enter fullscreen mode Exit fullscreen mode

Open up your browser to see a small React Location logo at the left bottom of the screen.

React Location Logo

Click it to see the routing details and how easy it is to debug your app.

React Locations Devtools

Next, add some data caching as well through a simple cache plugin. You can install it with the following command.

yarn add @tanstack/react-location-simple-cache
Enter fullscreen mode Exit fullscreen mode

Let's configure the simple cache now by importing it and initiating a cache instance.

import { ReactLocationSimpleCache } from "@tanstack/react-location-simple-cache";

// Set up a ReactLocation SimpleCache instance
const routeCache = new ReactLocationSimpleCache();
Enter fullscreen mode Exit fullscreen mode

We will define a cache expiration/invalidate time as well.

{
          path: "countries",
          element: <Countries />,
          loader: routeCache.createLoader(
            async () => {
              let { data } = await getCountries();
              return {
                countries: data,
              };
            },
            {
              maxAge: 1000 * 10, // 10 seconds
            }
          ),
  ..
}
Enter fullscreen mode Exit fullscreen mode

Let's see how we can navigate through the Link component and useNavigation..

Let’s use the Link component in our Navigation(HOC), which will comprise a navigation bar with several links.

import React from "react";
import AppBar from "@mui/material/AppBar";
import Grid from "@mui/material/Grid";
import Toolbar from "@mui/material/Toolbar";
import { Link } from "@tanstack/react-location";

const getActiveProps = () => {
  return {
    style: {
      fontWeight: "bold",
      color: "white",
      textDecoration: "none",
    },
  };
};

const Navigation = ({
  children,
}: {
  children: React.ReactNode;
}): JSX.Element => {
  return (
    <Grid container spacing={4}>
      <Grid item xs={12}>
        <AppBar position="static">
          <Toolbar>
            <Grid container spacing={1}>
              <Grid item xs={1}>
                <Link
                  to="/"
                  getActiveProps={getActiveProps}
                  activeOptions={{ exact: true }}
                >
                  Home
                </Link>
              </Grid>
              <Grid item xs={1}>
                <Link to="countries" getActiveProps={getActiveProps}>
                  Countries
                </Link>
              </Grid>
            </Grid>
          </Toolbar>
        </AppBar>
      </Grid>
      <Grid item xs={12} />
      <Grid item xs={12}>
        {children}
      </Grid>
    </Grid>
  );
};

export default Navigation;
Enter fullscreen mode Exit fullscreen mode

If you look closely, you can see that we have defined getActiveProps with some style properties that help us identify the current route.

Home Countries

You can go through the full demo project source code through this GitHub repo or play around with the deployed link.

React Location vs. other React routing solutions

React Location and React Router have many similarities, but React Location offers many features that provide a better developer experience. Here is a detailed feature comparison of React Location with React Router and Reach Router:

React Location Comparison Chart

Conclusion

React Location is an excellent routing solution for React applications. With features like advanced search params, dev tools, code splitting, route loaders, and async routes, React Location is a worthy alternative to React Router.

You can easily integrate React Location with external caches and storage like React Query, Apollo, SWR, and RTKQuery. The only downside of React Location is that it doesn't support SSR route matching or loading and hydration yet, but these features will be available soon.

React Location certainly needs more attention in the React community. React Location also has a grooving community you can find on StackOverflow, Discord, and GitHub discussions. Finally, I invite you all to try out React Location and see how fantastic a routing solution is.


Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket signup

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — start monitoring for free.

Top comments (0)