Skip to content

Commit

Permalink
docs: data fetching memoizing
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Dec 2, 2022
1 parent f12f1d8 commit a97849e
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 4 deletions.
157 changes: 154 additions & 3 deletions www/docs/getting-started/data-fetching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ 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

<SandpackEditor startRoute='/posts/1/'>

```tsx
Expand All @@ -37,9 +39,7 @@ const Router = createBrowserRouter({
{
path: "/:postId",
getData: async ({ params }) => {
const resp = await fetch(
`${API}/posts/${params.postId}/`
);
const resp = await fetch(`${API}/posts/${params.postId}`);

if (!resp.ok) throw new HttpError(404);
return resp.json();
Expand Down Expand Up @@ -130,3 +130,154 @@ Above is a simple "List, detail" view of imaginary blog posts. Along the side is
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.

<SandpackEditor startRoute='/posts/1/'>

```tsx
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 />;
}
```

```tsx PostsPage.tsx
import { Link, RouteComponentDataProps } from "found";
import type { Post } from "./PostPage.tsx";

export default function PostsPage({
data,
children,
}: RouteComponentDataProps<{ posts: Post[] }>) {
return (
<div className="posts">
<nav className="posts--nav">
{data?.posts.map((post) => (
<Link key={post.id} to={`/posts/${post.id}`}>
{post.title}
</Link>
))}
</nav>
<section>{children}</section>
</div>
);
}
```

```tsx PostPage.tsx
import type { RouteComponentDataProps } from "found";

export interface Post {
id: string;
title: string;
body: string;
}

export default function PostPage({
data: post,
children,
}: RouteComponentDataProps<Post>) {
return (
<div>
<h2>{post.title}</h2>
<p>{post.body}</p>

<section>{children}</section>
</div>
);
}
```

```css styles.css
.posts {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
grid-gap: 1rem;
}

.posts--nav {
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.posts--nav > a {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
```

</SandpackEditor>

:::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.
3 changes: 2 additions & 1 deletion www/src/components/SandpackEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export default function SandpackEditor({
customSetup={{
dependencies: {
...dependencies,
found: '*',
'found': '*',
'memoize-one': '^6.0.0',
},
}}
/>
Expand Down

0 comments on commit a97849e

Please sign in to comment.