-
Notifications
You must be signed in to change notification settings - Fork 358
Improve data fetching docs #1179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
LadyBluenotes
merged 36 commits into
solidjs:v2-docs
from
amirhhashemi:fix-use-submission-situation
Aug 26, 2025
Merged
Changes from 21 commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
39f3b9f
Fix useSubmission
amirhhashemi f3c6bd0
Update
amirhhashemi e9d0891
Update
amirhhashemi 5a7ec5c
Update info callout titles (#1207)
amirhhashemi aec869e
update
amirhhashemi 73da3ce
update
amirhhashemi ebd6265
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] b505134
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] 207a728
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] e138cd6
update
amirhhashemi 9c274ac
update
amirhhashemi eb6d2e0
update
amirhhashemi bca58d4
update
amirhhashemi 08af13a
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] f36f2f8
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] bf29f48
Merge branch 'main' into fix-use-submission-situation
kodiakhq[bot] 65900a3
update
amirhhashemi 01f6654
update
amirhhashemi 319c28d
update
amirhhashemi a1cad3c
update
amirhhashemi 0eff6f4
update
amirhhashemi da0fa78
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi d164be5
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi 7f04981
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi a3f4d0c
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi b6a2b2d
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi 784bb63
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi 5fe813a
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi 3f72aca
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi 972cc02
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi e4cdc46
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi 68aa8a6
Update src/routes/solid-router/concepts/queries.mdx
amirhhashemi 51c9c7d
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi 8f2a98e
Update src/routes/solid-start/guides/data-fetching.mdx
amirhhashemi 8cc3137
apply review results
amirhhashemi 9d3df64
experiment with new structure
amirhhashemi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| "nesting.mdx", | ||
| "layouts.mdx", | ||
| "alternative-routers.mdx", | ||
| "queries.mdx", | ||
| "actions.mdx" | ||
| ] | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
|
||
| 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. | ||
|
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). | ||
|
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`. | ||
|
amirhhashemi marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```tsx | ||
|
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). | ||
|
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. | ||
|
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. | ||
|
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. | ||
|
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. | ||
|
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. | ||
|
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. | ||
|
amirhhashemi marked this conversation as resolved.
Outdated
|
||
|
|
||
| ```tsx | ||
|
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. | ||
|
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. | ||
|
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). | ||
|
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`. | ||
|
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. | ||
|
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. | ||
|
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. | ||
|
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. | ||
|
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. | ||
|
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. | ||
|
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>; | ||
| } | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.