Skip to main content

Quick start

Found is a router for React applications with a focus on fetching performance and extensibility. Found enables "Render-as-You-Fetch" patterns for efficient code splitting and data fetching in your React applications with or without Suspense. Giving you unmatched control, and your user's the best experiences.

Found is designed to be extremely customizable. Almost all behavior and pieces can be changed or fully replaced. Including the path matching algorithm and route UI construction. This allows extensions such as Found Relay to provide first-class support for different use cases.

Installation and setup

Include the packages in your product via your favorite package manager:

npm i found

# or yarn
yarn add found

Basic usage

Found provides rich client-side route, for your Single Page Application. It allows you to define your web application in terms of the URL just like traditional multi-page browser navigation, while also efficiently updating the your page as the URL changes, only loading and changing the parts of your application that need to. This enables unmatched data fetching efficiency and code code splitting, while providing users with a fast, ultra-responsive browsering experience.

To get started, define the "routes", or urls your application supports and the UI they require.

import { createBrowserRouter, Link } from "found";

const routeConfig = [
  {
    path: "/",
    Component: () => (
      <div>
        <h1>Hello World</h1>
        <Link to="/about">About Us</Link>
      </div>
    ),
  },
  {
    path: "/about",
    Component: () => (
      <div>
        <h1>About</h1>
        <p>Welcome to found</p>
      </div>
    ),
  },
];

const BrowserRouter = createBrowserRouter({ routeConfig });

export default function App() {
  return <BrowserRouter />;
}

Routes, can also be specified as JSX if you prefer, using Route, and makeRouteConfig:

import { createRoot } from "react-dom/client";
import { createBrowserRouter, Route } from "found";

const BrowserRouter = createBrowserRouter({
routeConfig: makeRouteConfig(
<>
<Route path="/" Component={HomePage} />
<Route path="/about" Component={AboutPage} />
</>
),
});

Nested routes

Best described by found's original inspiration react-router (who also borrowed the idea from Ember):

Nested Routing is the general idea of coupling segments of the URL to component hierarchy and data.

Nested routing is similar to "layouts" in server-side templating languages, allowings reuse of components, as well as connecting components with their data requirements.

import {
  createBrowserRouter,
  makeRouteConfig,
  Route,
  Link,
} from "found";

function AppPage({ children }) {
  return (
    <div>
      <nav>
        <Link to="/dashboard">Dashboard</Link>{" "}
        <Link to="/about">About</Link>
      </nav>
      <main>{children}</main>
    </div>
  );
}

const Router = createBrowserRouter({
  routeConfig: makeRouteConfig(
    <Route path="/" Component={AppPage}>
      <Route
        path="dashboard"
        Component={() => <h1>Dashboard page</h1>}
      />
      <Route path="about" Component={() => <h1>About page</h1>} />
    </Route>
  ),
});

export default function App() {
  return <Router />;
}

Here the route passes children to AppPage which will be the resolved nested routes component for the current location. In otherwords, when on /about the "About page" header will render and on /dashboard "Dashboard page". Because both routes start with / they are children of AppPage.

tip

You can use spa-routing to manage your links in an organized and well-typed way

Index routes

A URL will 404 if there is no exact match for it in the route config.

<Route path="/">
<Route path="customers" Component={CustomersPage}>
<Route path=":customerId" Component={CustomerPage} />
</Route>
</Route>

For the above config only /customers/1/ will render, while '/customers' will 404. However if want to render a customers list page, we can add an "index route", which is a route with no path.

<Route path="/">
<Route path="customers">
<Route Component={CustomersPage} />
<Route path=":customerId" Component={CustomerPage} />
</Route>
</Route>

If you need a parent route to function as a parent while also rendering as the index route when there is no customer mark the parent route with allowAsIndex.

<Route path="/">
<Route path="customers" allowAsIndex Component={CustomersPage}>
<Route path=":customerId" Component={CustomerPage} />
</Route>
</Route>

Dynamic route parameters

Routes, can also be parameterized, allowing you to represent state through URLs. Route components are passed match as a prop which contains params (amoung other things). Any component can access the current route props using useParams as well.

import {
  createBrowserRouter,
  makeRouteConfig,
  Route,
  Link,
} from "found";
import useParams from "found/useParams";

function CustomerPage({ children, match }) {
  const { customerId } = match.params;

  return (
    <div>
      <h1>Customer #{customerId}</h1>

      <h2>Orders</h2>
      <nav>
        <Link to={`/customers/${customerId}/orders/1`}>Order 1</Link>{" "}
        <Link to={`/customers/${customerId}/orders/2`}>Order 2</Link>
      </nav>
      <main>{children}</main>
    </div>
  );
}

function OrderPage() {
  const { orderId } = useParams();

  return <div>Order #{orderId}</div>;
}

const Router = createBrowserRouter({
  routeConfig: makeRouteConfig(
    <Route path="customers/:customerId" Component={CustomerPage}>
      <Route path="orders/:orderId" Component={OrderPage} />
    </Route>
  ),
});

export default function App() {
  return <Router />;
}

tip

All params are accessible to every component and routes not just the Route that declares it.

Route params are flexible and allow "splats" as well as regular expressions, see path for all the details.

Loading data

Found has out-of-the-box support for efficient data fetching. Add a getData function to any route and it's return value will be passed to the Route as a data prop. see Data fetching for more details about how found avoids waterfalls and overfetching.

import "./styles.css";

import { createBrowserRouter, HttpError } from "found";
import PostsPage from "./PostsPage";
import PostPage from "./PostPage";

async function fetchFromApi(path: string) {
  const resp = await fetch(`https://dummyjson.com${path}`);

  if (!resp.ok) throw new HttpError(404);
  return resp.json();
}

const Router = createBrowserRouter({
  routeConfig: [
    {
      path: "/",
      getData: () => fetchFromApi("/posts"),
      Component: PostsPage,
    },
    {
      path: "posts/:postId",
      getData: async ({ params }) =>
        fetchFromApi(`/posts/${params.postId}/`),
      Component: PostPage,
    },
  ],
});

export default function App() {
  return <Router />;
}

IE 11

found depends on async iterators, which requires a polyfill of Symbol.asyncIterator for IE11. Core-js provides one if needed, import before importing found.

import "core-js/es/symbol/async-iterator";