react-location

<img src="https://static.scarf.sh/a.png?x-pxid=d988eb79-b0fc-4a2b-8514-6a1ab932d188" />

Usage no npm install needed!

<script type="module">
  import reactLocation from 'https://cdn.skypack.dev/react-location';
</script>

README

⚛️ React-Location (Beta)

Enterprise Routing for React

#TanStack semantic-release Join the discussion on Github Best of JS

Enjoy this library? Try the entire TanStack! React Query, React Table, React Form, React Charts

Features

  • Familiar API inspired by React Router & Next.js
  • JSON Search Param API
  • Search Param compression
  • Full ⌘/Ctrl-click Support
  • Transactionally Safe Location Updates
  • Relative Routing + Links

Table of Contents

Getting Started

  1. Install react-location@next
npm install react-location@next --save
# or
yarn add react-location@next
  1. Import and use react-location
import { React } from 'react';
import { ReactLocation, Route, Routes, Link, useParams } from 'react-location';

export function App() {
  return (
    <ReactLocation>
      <nav>
        <Link to="/">Home</Link>
        <Link to="dashboard">Dashboard</Link>
        <Link to="invoices">Invoices</Link>
        <Link to="https://github.com/tannerlinsley/react-location">
          Github - React Location
        </Link>
      </nav>
      <div>
        <Route path="/" element={<div>This is Home</div>} />
        <Route path="dashboard" element={<div>This is the Dashboard</div>} />
        <Route path="invoices" element={<Invoices />}></Route>
      </div>
    </ReactLocation>
  );
}

function Invoices() {
  return (
    <Routes>
      <Route path="/">
        <div>Invoices</div>
        <ul>
          <li>
            <Link to="new">New Invoice</Link>
          </li>
          <li>
            <Link to="1">Invoice 1</Link>
          </li>
          <li>
            <Link to="2">Invoice 2</Link>
          </li>
          <li>
            <Link to="3">Invoice 3</Link>
          </li>
        </ul>
      </Route>
      <Route
        path="new"
        element={
          <>
            <Link to="..">Back</Link>
            <div>This is a new invoice!</div>
          </>
        }
      />
      <Route path=":invoiceID" element={<Invoice />} />
    </Routes>
  );
}

function Invoice() {
  const params = useParams();
  const search = useSearch();
  const navigate = useNavigate();

  const isPreviewOpen = search.previewState?.isOpen;

  const togglePreview = () =>
    navigate({
      search: old => ({
        ...old,
        previewState: {
          ...(old.previewState ?? {}),
          isOpen: !old.previewState.isOpen,
        },
      }),
    });

  return (
    <div>
      <Link to="..">Back</Link>
      <div>This is invoice #{params.invoiceID}</div>
      <div>
        <button onClick={togglePreview}>
          {isPreviewOpen ? 'Hide' : 'Show'} Preview
        </button>
      </div>
      {isPreviewOpen ? <div>This is a preview!</div> : null}
    </div>
  );
}

Documentation

ReactLocation

Required: true

The ReactLocation component is the root Provider component for react-location in your app. Render it at least once in the highest sensible location within your application. You can also use this component to preserve multiple location instances in the react tree at the same, which is useful for things like route animations or location mocking.

Prop Required Description
history false The history object to be used internally by react-location
basepath false The basepath prefix for all URLs (not-supported for memory source histories)
children true The children to pass the location context to

Example: Basic

import { ReactLocation } from 'react-location';

return (
  <ReactLocation>
    <div>Your Application</div>
  </ReactLocation>
);

Example: Memory History

import { createMemoryHistory, ReactLocation } from 'react-location';

const history = createMemoryHistory();

return (
  <ReactLocation history={history}>
    <div>...</div>
  </ReactLocation>
);

Route

The Route component is used to render content when its path routees the current history's location. It is generally used for routing purposes. It also provides the new relative routeing path to child Route components, allowing for clean nested route definition.

Prop Required Description
path true The path to route (relative to the nearest parent Route component or root basepath)
element The content to be rendered when the path routees the current location

Example

<Route path="about" element="About Me" />

Routes

The Routes component is used to selectively render the first child component that is av valid route and/or provide fallbacks. This is useful for:

  • Nesting and Relative Routes
  • Matching index routes and hard-coded routes before dynamic ones
  • Fallback/Wildcard routes

Example - Route Params

render(
  <Route path="invoices">
    <Routes>
      <Route
        path="/"
        element="This route would route and display at `/invoices/`"
      />
      <Route
        path="new"
        element="This route would route and display at `/invoices/new`"
      />
      <Route path=":invoiceID" element={<Invoice />} />
    </Routes>
  </Route>
);

function Invoice() {
  const params = useParams();

  return (
    <div>
      <Link to="..">Back</Link>
      <div>This is invoice #{params.invoiceID}</div>
    </div>
  );
}

Example - Default / Fallback Route

render(
  <Routes>
    <Route path="/" element="his route would route and display at `/`" />
    <Route
      path="about"
      element="This route would route and display at `/about`"
    />
    <Route
      path="*"
      element="This element would be rendered as the fallback when no matches are found"
    />
  </Routes>
);

Example - Default / Fallback Route with redirect

render(
  <Routes>
    <Route path="/" element="This route would route and display at `/`" />
    <Route
      path="about"
      element="This route would route and display at `/about`"
    />
    {/* Redirect all other routes to `/` */}
    <Route path="*" element={<Navigate to="/" />} />
  </Routes>
);

Link

The Link component allows you to generate links for internal navigation, capable of updating the:

  • Pathname
  • Search Parameters
  • Location State
  • Hash
  • Push vs. Replace

The links generated by it are designed to work perfectly with Open in new Tab + ctrl + left-click and Open in new window.... They are also capable of receiving "active" props (depending on the activeOptions passed) to decorate the link when the link is currently active relative to the current location.

Prop Description
...NavigateProps All properties for the component method are supported here.
activeOptions?: { exact?: boolean, includeHash?: boolean} Defaults to { exact: false, includeHash: false }
getActiveProps: () => PropsObject A function that is passed the Location API and returns additional props for the active state of this link. These props override other props passed to the link (style's are merged, className's are concatenated)

Example: The basics

render(
  <div>
    <Link to="/home">I will navigate to `/home`</Link>
    <Link to="todos">
      I will navigate to `./todos`, relative to the current location
    </Link>
    <Link to="..">I will navigate up one level in the location hierarchy.</Link>
    <Link to="." hash="somehash">
      I will update the hash to `somehash` at the current location
    </Link>
    <Link to="/search" search={{ q: 'yes' }}>
      I will navigate to `/search?q=yes`
    </Link>
    <Link
      to="."
      search={{
        someParams: true,
        otherParams: 'gogogo',
        object: { nested: { list: [1, 2, 3], hello: 'world' } },
      }}
    >
      I will navigate to the current location +
      `?someParams=true&otherParams=gogogo&object=~(nested~(list~(~1~2~3)~hello~%27world))`
    </Link>
    <Link
      search={({ removeThis, ...rest }) => ({
        ...rest,
        addThis: 'This is new!',
      })}
    >
      I will add `addThis='This is new!' and also remove the `removeThis` param
      to/from the search params on the current page
    </Link>
  </div>
);

Example: Using getActiveProps

The following link will be green with /about as the current location.

<Link
  to="/about"
  getActiveProps={location => ({
    style: { color: 'green' },
  })}
>
  About
</Link>

useLocation

The useLocation hook returns the current React Location instance from context when used.

Example

import { useLocation } from 'react-location';

export function MyComponent() {
  const location = useLocation();
  // use location...
}

Navigate

When renderd, the Navigate component will declaratively and relatively navigate to any route.

Type

type NavigateProps = {
  to?: string | null;
  pathname?: string | null;
  search?: Updater<SearchObj>;
  state?: Updater<StateObj>;
  hash?: Updater<string>;
  replace?: boolean;
};

Example

function App () {
  return <Navigate to='./about'>
}

useNavigate

The useNavigate hook allows you to programmatically navigate your application.

Usage

function MyComponent() {
  const navigate = useNavigate();

  const onClick = () => {
    navigate('./about', { replace: true });
  };

  return <button onClick={onClick}>About</button>;
}

useMatch

The useMatch hook allows you to programmatically test a path for a route within the closest relative route. If a path is match, it will return an object of route params detected, even if this is an empty object. If a path doesn't match, it will return false.

Usage

function App() {
  const match = useMatch();

  // If the path is '/'
  match('/'); // {}
  match(':teamId'); // false

  // If the path is `/team-1'
  match('/'); // {}
  match('/', { exact: true }); // false
  match(':teamId'); // { teamId: 'team-1 }

  return (
    <Routes>
      <Route path="/" element="Hello!" />
      <Route path=":teamId" element="Hello!" />
    </Routes>
  );
}

SSR

Server-side rendering is easy with react-location. Use createMemoryHistory and ReactLocation to mock your app into a specific state for SSR:

import {
  createBowserHistory
  createMemoryHistory,
  ReactLocation,
} from 'react-location';

let history;

if (typeof document !== 'undefined') {
  history = createBowserHistory();
} else {
  history = createMemoryHistory(['/blog/post/2]);
}

return (
  <ReactLocation history={history}>
    <div>...</div>
  </ReactLocation>
);

Inspiration

All of these libraries offered a lot of guidance and good patterns for writing this library: