Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
39f3b9f
Fix useSubmission
amirhhashemi May 16, 2025
f3c6bd0
Update
amirhhashemi Jun 8, 2025
e9d0891
Update
amirhhashemi Jun 11, 2025
5a7ec5c
Update info callout titles (#1207)
amirhhashemi Jun 18, 2025
aec869e
update
amirhhashemi Jul 2, 2025
73da3ce
update
amirhhashemi Jul 3, 2025
ebd6265
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] Jul 30, 2025
b505134
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] Aug 1, 2025
207a728
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] Aug 13, 2025
e138cd6
update
amirhhashemi Aug 5, 2025
9c274ac
update
amirhhashemi Aug 9, 2025
eb6d2e0
update
amirhhashemi Aug 14, 2025
bca58d4
update
amirhhashemi Aug 16, 2025
08af13a
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] Aug 16, 2025
f36f2f8
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] Aug 20, 2025
bf29f48
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] Aug 20, 2025
65900a3
update
amirhhashemi Aug 23, 2025
01f6654
update
amirhhashemi Aug 24, 2025
319c28d
update
amirhhashemi Aug 25, 2025
a1cad3c
update
amirhhashemi Aug 25, 2025
0eff6f4
update
amirhhashemi Aug 25, 2025
da0fa78
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
d164be5
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
7f04981
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
a3f4d0c
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
b6a2b2d
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
784bb63
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
5fe813a
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
3f72aca
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
972cc02
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
e4cdc46
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
68aa8a6
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi Aug 26, 2025
51c9c7d
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Aug 26, 2025
8f2a98e
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi Aug 26, 2025
8cc3137
apply review results
amirhhashemi Aug 26, 2025
9d3df64
experiment with new structure
amirhhashemi Aug 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/middleware/legacy-routes-redirect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,8 @@ const LEGACY_ROUTES = {

"/solid-router/reference/response-helpers/revalidate":
"/solid-router/reference/data-apis/revalidate",

"/solid-start/guides/data-loading": "/solid-start/guides/data-fetching",
} as const;

function isLegacyRoute(path: string): path is keyof typeof LEGACY_ROUTES {
Expand Down
470 changes: 382 additions & 88 deletions src/routes/solid-router/concepts/actions.mdx

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/routes/solid-router/concepts/data.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"nesting.mdx",
"layouts.mdx",
"alternative-routers.mdx",
"queries.mdx",
"actions.mdx"
]
}
}
317 changes: 317 additions & 0 deletions src/routes/solid-router/concepts/queries.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
---
title: "Queries"
---

Fetching data from a server or other sources is key to applications.
Doing this properly requires keeping the data up to date and handling pending and error states, which can be complex.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

Queries are the core building blocks for data fetching in Solid Router.
They provide an elegant solution for managing data fetching.

## Defining queries

They are defined using the [`query` function](/solid-router/reference/data-apis/query).
It wraps the data-fetching logic and extends it with powerful capabilities like [request deduplication](#deduplication) and [automatic revalidation](#revalidation).

The `query` function takes two parameters: a **fetcher** and a **name**.

- The **fetcher** is an asynchronous function that fetches data from any source, such as a remote API.
- The **name** is a unique string used to identify the query.
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.

```tsx
import { query } from "@solidjs/router";

const getUserProfileQuery = query(async (userId: string) => {
const response = await fetch(
`https://api.example.com/users/${encodeURIComponent(userId)}`
);
const json = await response.json();

if (!response.ok) {
throw new Error(json?.message ?? "Failed to load user profile.");
}

return json;
}, "userProfile");
```

In this example, a query is defined that fetches a user's profile from an API.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated
The fetcher will throw an error if the request fails, which can be handled by an [`<ErrorBoundary>`](/reference/components/error-boundary).
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

## Using queries in components

Defining a query does not, by itself, fetch any data.
To call a query and make its data available to a component, the [`createAsync` primitive](/solid-router/reference/data-apis/create-async) is used.

`createAsync` takes an asynchronous function and returns a signal that tracks its result.
Because a query is essentially an asynchronous function, it can be used with `createAsync`.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

```tsx
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated
import { For, Show } from "solid-js";
import { query, createAsync } from "@solidjs/router";

const getArticlesQuery = query(async () => {
// ... Fetches a list of articles from an API.
}, "articles");

function Articles() {
const articles = createAsync(() => getArticlesQuery());

return (
<Show when={articles()}>
<For each={articles()}>{(article) => <p>{article.title}</p>}</For>
</Show>
);
}
```

:::tip
When working with complex values, like an array or a deeply nested object, the [`createAsyncStore` primitive](/solid-router/reference/data-apis/create-async-store) might be more performant and ergonomic.
It works similar to `createAsync`, but provides a [store](/concepts/stores).
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated
:::

## Handling pending and error states

The `createAsync` primitive is designed to work with Solid's native components for handling asynchronous states.
It communicates its pending state to a [`<Suspense>`](/reference/components/suspense) boundary for showing pending fallbacks.
It also propagates errors to an [`<ErrorBoundary>`](/reference/components/error-boundary) for displaying error messages.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

By accessing a query's data within these two components, pending and error states can be handled gracefully.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

```tsx
import { Suspense, ErrorBoundary, For } from "solid-js";
import { query, createAsync } from "@solidjs/router";

const getNewsQuery = query(async () => {
// ... Fetches the latest news from an API.
}, "news");

function NewsFeed() {
const news = createAsync(() => getNewsQuery());

return (
<ErrorBoundary fallback={<p>Could not fetch news.</p>}>
<Suspense fallback={<p>Loading news...</p>}>
<ul>
<For each={news()}>{(item) => <li>{item.headline}</li>}</For>
</ul>
</Suspense>
</ErrorBoundary>
);
}
```

## Preloading data

Preloading data is a powerful technique for making an application feel faster by fetching the data needed for a future page before a user navigates to it.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated
Solid Router initiates preloading in two scenarios: when a user indicates an intent to navigate (for example, by hovering over a link), and when a route's component is rendering.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated
This ensures that data fetching begins at the earliest possible moment, often allowing data to be ready when the component renders.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

Preloading is configured using the [`preload`](/solid-router/reference/preload-functions/preload) prop on a [`Route`](/solid-router/reference/components/route).
This prop accepts a function that calls one or more queries.
When triggered, the preload function executes the queries, and their results are stored in a short-lived internal cache.
When the user completes the navigation and the destination route's component begins to render, the `createAsync` call in the component will use the preloaded data.
Because of the [deduplication mechanism](#deduplication), it will not send a redundant network request.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

```tsx
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated
import { Show } from "solid-js";
import { Route, query, createAsync } from "@solidjs/router";

const getProductQuery = query(async (id: string) => {
// ... Fetches product details for the given ID.
}, "product");

function ProductDetails(props) {
const product = createAsync(() => getProductQuery(props.params.id));

return (
<Show when={product()}>
<h1>{product().name}</h1>
</Show>
);
}

function preloadProduct({ params }: { params: { id: string } }) {
getProductQuery(params.id);
}

function Routes() {
return (
<Route
path="/products/:id"
component={ProductDetails}
preload={preloadProduct}
/>
);
}
```

In this example, hovering a link to `/products/:id` triggers `preloadProduct`.
When the `ProductDetails` component renders, its `createAsync` call will instantly resolve with the preloaded data.

## Deduplication

A key feature of queries is their ability to deduplicate requests.
It is a short-term mechanism to prevent redundant data fetching in quick succession.

A common example of this is preloading.
When a user hovers over a link, the application can begin preloading the data for the destination page.
If the user then clicks the link, the query has already been executed, and the data is available instantly without a second network request.
This deduplication is fundamental to the performance of Solid Router applications.

This deduplication also applies when multiple components on the same page use the same query.
As long as a query is actively being used by one component, Solid Router will not refetch its data and uses the cached data.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

## Revalidation

Data on the server can change.
To prevent the UI from becoming stale, Solid Router provides mechanisms for revalidating a query.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

The most common scenario is automatic revalidation.
After an [action](/solid-router/concepts/actions) successfully completes, Solid Router automatically revalidates all active queries on the page.
Learn more about automatic revalidation in the [actions documentation](/solid-router/concepts/actions#automatic-data-revalidation).
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

For more fine-grained control, revalidation can be triggered manually using the [`revalidate`](/solid-router/reference/data-apis/revalidate) function.
The `revalidate` function takes a query key (or an array of keys) to target specific queries for revalidation.
Each query exposes two properties for this purpose: `key` and `keyFor`.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

- `query.key` is the base key for the query, which targets all queries.
Using this key will revalidate all data previously fetched by that query, regardless of the arguments used.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated
- `query.keyFor(arguments)` is a function that generates a key for a specific set of arguments.
This allows for targeting and revalidating only a single, specific query.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

```tsx
import { For } from "solid-js";
import { query, createAsync, revalidate } from "@solidjs/router";

const getProjectsQuery = query(async () => {
// ... Fetches a list of projects.
}, "projects");

const getProjectTasksQuery = query(async (projectId: string) => {
// ... Fetches a list of tasks for a project.
}, "projectTasks");

function Projects() {
const projects = createAsync(() => getProjectsQuery());

function refetchAllTasks() {
revalidate(getProjectTasksQuery.key);
}

return (
<div>
<button onClick={refetchAllTasks}>Refetch all tasks</button>
<For each={projects()}>{(project) => <Project id={project.id} />}</For>
</div>
);
}

function Project(props: { id: string }) {
const tasks = createAsync(() => getProjectTasksQuery(props.id));

function refetchTasks() {
revalidate(getProjectTasksQuery.keyFor(props.id));
}

return (
<div>
<button onClick={refetchTasks}>Refetch tasks for this project</button>
<For each={project.tasks}>{(task) => <div>{task.title}</div>}</For>
</div>
);
}
```

## Streaming

In a traditional server-rendered application, the user must wait for all data to be fetched on the server before the page is rendered and sent to the browser.
This can lead to a slow initial page load if some queries are slow.
Streaming solves this problem by allowing the server to send the page's HTML shell immediately and "stream" the content for data-dependent sections as they become ready.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

When a query is accessed during a server-side render, Solid suspends the UI while it waits for the data to resolve.
By default, this suspense affects the entire page.

The key to controlling this behavior is suspense boundaries.
A suspense boundary is a region in the component tree defined by a [`<Suspense>`](/reference/components/suspense) component.
It acts like a container that isolates asynchronous behavior to a specific part of the page.

Anything inside the suspense boundary will be managed by Solid's concurrency system, and if it's not ready, the boundary's fallback UI will be displayed.
This allows the rest of the page, which is outside the boundary, to be rendered and sent to the user immediately.
Once the data inside a suspense boundary resolves, the server streams the final HTML for that section to the browser, replacing the fallback.
This allows the user to see and interact with the majority of the page much faster.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

```tsx
import { Suspense, For } from "solid-js";
import { query, createAsync } from "@solidjs/router";

const getAccountStatsQuery = query(async () => {
// ... Fetches account statistics.
}, "accountStats");

const getRecentTransactionsQuery = query(async () => {
// ... Fetches a list of recent transactions.
}, "recentTransactions");

function Dashboard() {
const stats = createAsync(() => getAccountStatsQuery());
const transactions = createAsync(() => getRecentTransactionsQuery());

return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<p>Loading account stats...</p>}>
<For each={stats()}>
{(stat) => (
<p>
{stat.label}: {stat.value}
</p>
)}
</For>
</Suspense>

<Suspense fallback={<p>Loading recent transactions...</p>}>
<For each={transactions()}>
{(transaction) => (
<h2>
{transaction.description} - {transaction.amount}
</h2>
)}
</For>
</Suspense>
</div>
);
}
```

In this example, each `<Suspense>` component creates its own independent boundary.
The server can start streaming the heading `<h1>Dashboard</h1>` immediately, while the two data-dependent sections (`stats` and `transactions`) are handled separately.
If the `transactions` query is slow, only its boundary will show a pending fallback, and the `stats` will still render as soon as their data is ready.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

### When to disable streaming

While streaming is powerful, there are scenarios where it is preferable to wait for the data to load on the server.
For these cases, the `deferStream` option on `createAsync` can be used.

By setting `deferStream` to `true`, the server will wait for the query to resolve before sending the initial HTML.

One reason to disable streaming is for Search Engine Optimization (SEO).
Some search engine crawlers may not wait for streamed content to be fully loaded.
If a piece of data is critical for SEO, such as a page title or meta description, it is best to ensure it is part of the initial server response.
Comment thread
amirhhashemi marked this conversation as resolved.
Outdated

```tsx
import { query, createAsync } from "@solidjs/router";

const getArticleQuery = query(async () => {
// ... Fetches an article.
}, "article");

function ArticleHeader() {
const article = createAsync(() => getArticleQuery(), {
deferStream: true,
});

return <h1>{article()?.title}</h1>;
}
```
Loading