Skip to main content

Data fetching

One of the most powerful aspects of found is it's built in support for efficient data fetching. Routes, can specify getData functions to fetch from API's or other sources.

Loading data on navigation

import "./styles.css";

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

const API = "https://dummyjson.com";

const Router = createBrowserRouter({
  routeConfig: [
    {
      path: "posts",
      getData: async () => {
        const resp = await fetch(`${API}/posts`);

        if (!resp.ok) throw new HttpError(404);
        return resp.json();
      },
      Component: PostsPage,
      children: [
        {
          path: "/:postId",
          getData: async ({ params }) => {
            const resp = await fetch(`${API}/posts/${params.postId}`);

            if (!resp.ok) throw new HttpError(404);
            return resp.json();
          },
          Component: PostPage,
        },
      ],
    },
  ],
});

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

Above is a simple "List, detail" view of imaginary blog posts. Along the side is a list of of posts and clicking on any navigates to the "detail" view of the post. Even though these routes are nested, data is fetched in parallel. /posts and /posts/1 are triggered together and the UI waits for both to complete before rendering.

Avoiding unnecessary server trips

The example above isn't as efficient as it could be. When you switch routes both products and the product detail route are updated, which triggers fetching the list of posts again, even though we already have it and it probably hasn't changed.

For simple cases "memoizing" our getData function based on the the input each function needs works great.

import "./styles.css";

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

const API = "https://dummyjson.com";

const Router = createBrowserRouter({
  routeConfig: [
    {
      path: "posts",
      Component: PostsPage,
      getData: memoize(
        async () => {
          const resp = await fetch(`${API}/posts`);

          if (!resp.ok) throw new HttpError(404);
          return resp.json();
        },
        // Only fetch once, the first time
        () => true
      ),
      children: [
        {
          path: "/:postId",
          Component: PostPage,
          getData: memoize(
            async ({ params }) => {
              const resp = await fetch(
                `${API}/posts/${params.postId}`
              );

              if (!resp.ok) throw new HttpError(404);
              return resp.json();
            },
            // Only fetch again when `postId` changes
            ([{ params: lastParams }], [{ params }]) =>
              lastParams.postId === params.postId
          ),
        },
      ],
    },
  ],
});

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

info

Why doesn't Found memoize getData calls automatically?

Because generalized caching is hard! Found can't always know what the right behavior is for your app, so instead of guessing we give you the tools to do it your way.

This example is over simplified and doesn't cover the richness in use-cases that data fetching entails. You probably do want to refetch posts occasionally to ensure the data stays fresh. Maybe you want to the data to loading as soon as new posts are available, or when the user refocuses the page after being inactive. This where data fetching libraries like react-query, relay, apollo and others start to make sense.

These libraries all handle the hard work of syncing server data to browsers and keeping it fresh and up to date. Found is a great companion to these libraries! Found provides the hooks for efficient data loading and triggers by mapping your data needs to the URL.