Skip to content

Commit 534a8e5

Browse files
amirhhashemikodiakhq[bot]LadyBluenotes
committed
Improve data fetching docs (#1179)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Sarah <gerrardsarah@gmail.com>
1 parent 60681ac commit 534a8e5

File tree

26 files changed

+1604
-852
lines changed

26 files changed

+1604
-852
lines changed

src/middleware/legacy-routes-redirect.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ const LEGACY_ROUTES = {
180180

181181
"/solid-router/reference/response-helpers/revalidate":
182182
"/solid-router/reference/data-apis/revalidate",
183+
184+
"/solid-start/guides/data-loading": "/solid-start/guides/data-fetching",
183185
} as const;
184186

185187
function isLegacyRoute(path: string): path is keyof typeof LEGACY_ROUTES {

src/routes/solid-router/concepts/actions.mdx

Lines changed: 382 additions & 88 deletions
Large diffs are not rendered by default.

src/routes/solid-router/concepts/data.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"nesting.mdx",
99
"layouts.mdx",
1010
"alternative-routers.mdx",
11+
"queries.mdx",
1112
"actions.mdx"
1213
]
13-
}
14+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"title": "Data fetching",
3+
"pages": ["queries.mdx", "streaming.mdx", "revalidation.mdx", "how-to"]
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"title": "How to",
3+
"pages": ["preload-data.mdx", "handle-error-and-loading-states.mdx"]
4+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: "Handle pending and error states"
3+
---
4+
5+
The `createAsync` primitive is designed to work with Solid's native components for managing asynchronous states.
6+
It reports its pending state to the nearest [`<Suspense>` boundary](/reference/components/suspense) to display loading fallbacks, and propagate errors to an [`<ErrorBoundary>`](/reference/components/error-boundary) for handling and displaying error messages.
7+
8+
```tsx
9+
import { Suspense, ErrorBoundary, For } from "solid-js";
10+
import { query, createAsync } from "@solidjs/router";
11+
12+
const getNewsQuery = query(async () => {
13+
// ... Fetches the latest news from an API.
14+
}, "news");
15+
16+
function NewsFeed() {
17+
const news = createAsync(() => getNewsQuery());
18+
19+
return (
20+
<ErrorBoundary fallback={<p>Could not fetch news.</p>}>
21+
<Suspense fallback={<p>Loading news...</p>}>
22+
<ul>
23+
<For each={news()}>{(item) => <li>{item.headline}</li>}</For>
24+
</ul>
25+
</Suspense>
26+
</ErrorBoundary>
27+
);
28+
}
29+
```
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
title: "Preload data"
3+
---
4+
5+
Preloading data improves perceived performance by fetching the data for an upcoming page before the user navigates to it.
6+
7+
Solid Router initiates preloading in two scenarios:
8+
9+
- When a user indicates intent to navigate to the page (e.g., by hovering over a link).
10+
- When the route's component is rendering.
11+
12+
This ensures data fetching starts as early as possible, often making data ready once the component renders.
13+
14+
Preloading is configured using the [`preload`](/solid-router/reference/preload-functions/preload) prop on a [`Route`](/solid-router/reference/components/route).
15+
This prop accepts a function that calls one or more queries.
16+
When triggered, the queries execute and their results are stored in a short-lived internal cache.
17+
Once the user navigates and the destination route’s component renders, any `createAsync` calls within the page will consume the preloaded data.
18+
Thanks to the [deduplication mechanism](#deduplication), no redundant network requests are made.
19+
20+
```tsx {18-20,27}
21+
import { Show } from "solid-js";
22+
import { Route, query, createAsync } from "@solidjs/router";
23+
24+
const getProductQuery = query(async (id: string) => {
25+
// ... Fetches product details for the given ID.
26+
}, "product");
27+
28+
function ProductDetails(props) {
29+
const product = createAsync(() => getProductQuery(props.params.id));
30+
31+
return (
32+
<Show when={product()}>
33+
<h1>{product().name}</h1>
34+
</Show>
35+
);
36+
}
37+
38+
function preloadProduct({ params }: { params: { id: string } }) {
39+
getProductQuery(params.id);
40+
}
41+
42+
function Routes() {
43+
return (
44+
<Route
45+
path="/products/:id"
46+
component={ProductDetails}
47+
preload={preloadProduct}
48+
/>
49+
);
50+
}
51+
```
52+
53+
In this example, hovering a link to `/products/:id` triggers `preloadProduct`.
54+
When the `ProductDetails` component renders, its `createAsync` call will instantly resolve with the preloaded data.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
title: "Queries"
3+
---
4+
5+
Queries are the core building blocks for data fetching in Solid Router.
6+
They provide an elegant solution for managing data fetching.
7+
8+
## Defining queries
9+
10+
They are defined using the [`query` function](/solid-router/reference/data-apis/query).
11+
It wraps the data-fetching logic and extends it with powerful capabilities like [request deduplication](#deduplication) and [automatic revalidation](#revalidation).
12+
13+
The `query` function takes two parameters: a **fetcher** and a **name**.
14+
15+
- The **fetcher** is an asynchronous function that fetches data from any source, such as a remote API.
16+
- The **name** is a unique string used to identify the query.
17+
When a query is called, Solid Router uses this name and the arguments passed to the query to create a unique key, which is used for the internal deduplication mechanism.
18+
19+
```tsx
20+
import { query } from "@solidjs/router";
21+
22+
const getUserProfileQuery = query(async (userId: string) => {
23+
const response = await fetch(
24+
`https://api.example.com/users/${encodeURIComponent(userId)}`
25+
);
26+
const json = await response.json();
27+
28+
if (!response.ok) {
29+
throw new Error(json?.message ?? "Failed to load user profile.");
30+
}
31+
32+
return json;
33+
}, "userProfile");
34+
```
35+
36+
In this example, the defined query fetches a user's profile from an API.
37+
If the request fails, the fetcher will throw an error that will be caught by the nearest [`<ErrorBoundary>`](/reference/components/error-boundary) in the component tree.
38+
39+
## Using queries in components
40+
41+
Defining a query does not by itself fetch any data.
42+
To access its data, the query can be used with the [`createAsync` primitive](/solid-router/reference/data-apis/create-async).
43+
`createAsync` takes an asynchronous function, such as a query, and returns a signal that tracks its result.
44+
45+
```tsx
46+
import { For, Show } from "solid-js";
47+
import { query, createAsync } from "@solidjs/router";
48+
49+
const getArticlesQuery = query(async () => {
50+
// ... Fetches a list of articles from an API.
51+
}, "articles");
52+
53+
function Articles() {
54+
const articles = createAsync(() => getArticlesQuery());
55+
56+
return (
57+
<Show when={articles()}>
58+
<For each={articles()}>{(article) => <p>{article.title}</p>}</For>
59+
</Show>
60+
);
61+
}
62+
```
63+
64+
In this example, `createAsync` is used to call the query.
65+
Once the query completes, `articles` holds the result, which is then rendered.
66+
67+
:::tip
68+
When working with complex data types, such as arrays or deeply nested objects, the [`createAsyncStore` primitive](/solid-router/reference/data-apis/create-async-store) offers a more ergonomic and performant solution.
69+
It works like `createAsync`, but returns a [store](/concepts/stores) for easier state management..
70+
:::
71+
72+
## Deduplication
73+
74+
A key feature of queries is their ability to deduplicate requests, preventing redundant data fetching in quick succession.
75+
76+
One common use case is preloading: when a user hovers over a link, the application can begin preloading the data for the destination page.
77+
If the user then clicks the link, the query has already been completed, and the data is available instantly without triggering another network request.
78+
This mechanism is fundamental to the performance of Solid Router applications.
79+
80+
Deduplication also applies when multiple components on the same page use the same query.
81+
As long as at least one component is actively using the query, Solid Router will reuse the cached result instead of refetching the data.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
title: "Revalidation"
3+
---
4+
5+
Since server data can change, Solid Router provides mechanisms to revalidate queries and keep the UI up to date.
6+
7+
The most common case is **automatic revalidation**.
8+
After an [action](/solid-router/concepts/actions) completes successfully, Solid Router automatically revalidates all active queries on the page.
9+
For more details, see the [actions documentation](/solid-router/concepts/actions#automatic-data-revalidation).
10+
11+
For more fine-grained control, you can trigger revalidation manually with the [`revalidate` function](/solid-router/reference/data-apis/revalidate).
12+
It accepts a query key (or an array of keys) to target specific queries.
13+
Each query exposes two properties for this: `key` and `keyFor`.
14+
15+
- `query.key` is the base key for a query and targets all of its instances.
16+
Using this key will revalidate all data fetched by that query, regardless of the arguments provided.
17+
- `query.keyFor(arguments)` generates a key for a specific set of arguments, allowing you to target and revalidate only that particular query.
18+
19+
```tsx
20+
import { For } from "solid-js";
21+
import { query, createAsync, revalidate } from "@solidjs/router";
22+
23+
const getProjectsQuery = query(async () => {
24+
// ... Fetches a list of projects.
25+
}, "projects");
26+
27+
const getProjectTasksQuery = query(async (projectId: string) => {
28+
// ... Fetches a list of tasks for a project.
29+
}, "projectTasks");
30+
31+
function Projects() {
32+
const projects = createAsync(() => getProjectsQuery());
33+
34+
function refetchAllTasks() {
35+
revalidate(getProjectTasksQuery.key);
36+
}
37+
38+
return (
39+
<div>
40+
<button onClick={refetchAllTasks}>Refetch all tasks</button>
41+
<For each={projects()}>{(project) => <Project id={project.id} />}</For>
42+
</div>
43+
);
44+
}
45+
46+
function Project(props: { id: string }) {
47+
const tasks = createAsync(() => getProjectTasksQuery(props.id));
48+
49+
function refetchTasks() {
50+
revalidate(getProjectTasksQuery.keyFor(props.id));
51+
}
52+
53+
return (
54+
<div>
55+
<button onClick={refetchTasks}>Refetch tasks for this project</button>
56+
<For each={project.tasks}>{(task) => <div>{task.title}</div>}</For>
57+
</div>
58+
);
59+
}
60+
```
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
title: "Streaming"
3+
---
4+
5+
In traditional server-rendered applications, the server must fetch all data before rendering and sending the page to the browser.
6+
If some queries are slow, this delays the initial load.
7+
**Streaming** solves this by sending the page’s HTML shell immediately and progressively streaming data-dependent sections as they become ready.
8+
9+
When a query is accessed during a server-side render, Solid suspends the UI until the data resolves.
10+
By default, this suspension affects the entire page.
11+
12+
To control this behavior, you can use suspense boundaries - regions of the component tree defined by a [`<Suspense>` component](/reference/components/suspense).
13+
It isolates asynchronous behavior to a specific section of the page.
14+
15+
Content inside the boundary is managed by Solid’s concurrency system: if it isn’t ready, the boundary’s fallback UI is shown while the rest of the page renders and streams immediately.
16+
Once the data resolves, the server streams the final HTML for that section, replacing the fallback and letting users see and interact with most of the page much sooner.
17+
18+
```tsx
19+
import { Suspense, For } from "solid-js";
20+
import { query, createAsync } from "@solidjs/router";
21+
22+
const getAccountStatsQuery = query(async () => {
23+
// ... Fetches account statistics.
24+
}, "accountStats");
25+
26+
const getRecentTransactionsQuery = query(async () => {
27+
// ... Fetches a list of recent transactions.
28+
}, "recentTransactions");
29+
30+
function Dashboard() {
31+
const stats = createAsync(() => getAccountStatsQuery());
32+
const transactions = createAsync(() => getRecentTransactionsQuery());
33+
34+
return (
35+
<div>
36+
<h1>Dashboard</h1>
37+
<Suspense fallback={<p>Loading account stats...</p>}>
38+
<For each={stats()}>
39+
{(stat) => (
40+
<p>
41+
{stat.label}: {stat.value}
42+
</p>
43+
)}
44+
</For>
45+
</Suspense>
46+
47+
<Suspense fallback={<p>Loading recent transactions...</p>}>
48+
<For each={transactions()}>
49+
{(transaction) => (
50+
<h2>
51+
{transaction.description} - {transaction.amount}
52+
</h2>
53+
)}
54+
</For>
55+
</Suspense>
56+
</div>
57+
);
58+
}
59+
```
60+
61+
For example, each `<Suspense>` component creates its own independent boundary.
62+
The server can stream the heading `<h1>Dashboard</h1>` immediately, while the `stats` and `transactions` are handled separately.
63+
If the `transactions` query is slow, only its boundary will display a fallback, while `stats` will render as soon as its data is ready.
64+
65+
## When to disable streaming
66+
67+
While streaming is powerful, there are cases where it is better to wait for the data to load on the server.
68+
In these situations, you can use the `deferStream` option in `createAsync`.
69+
70+
When `deferStream` is set to `true`, the server waits for the query to resolve before sending the initial HTML.
71+
72+
A common reason to disable streaming is for Search Engine Optimization (SEO).
73+
Some search engine crawlers may not wait for streamed content to load.
74+
If critical data, such as a page title or meta description, affects SEO, it should be included in the initial server response.
75+
76+
```tsx
77+
import { query, createAsync } from "@solidjs/router";
78+
79+
const getArticleQuery = query(async () => {
80+
// ... Fetches an article.
81+
}, "article");
82+
83+
function ArticleHeader() {
84+
const article = createAsync(() => getArticleQuery(), {
85+
deferStream: true,
86+
});
87+
88+
return <h1>{article()?.title}</h1>;
89+
}
90+
```

0 commit comments

Comments
 (0)