diff --git a/src/middleware/legacy-routes-redirect.ts b/src/middleware/legacy-routes-redirect.ts
index fdf371f9b..647d1b38b 100644
--- a/src/middleware/legacy-routes-redirect.ts
+++ b/src/middleware/legacy-routes-redirect.ts
@@ -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 {
diff --git a/src/routes/solid-router/concepts/actions.mdx b/src/routes/solid-router/concepts/actions.mdx
index 53a96da9c..3d59eb63b 100644
--- a/src/routes/solid-router/concepts/actions.mdx
+++ b/src/routes/solid-router/concepts/actions.mdx
@@ -2,151 +2,445 @@
title: "Actions"
---
-When developing applications, it is common to need to communicate new information to the server based on user interactions.
-Actions are Solid Router’s solution to this problem.
+Many user interactions in an application involve changing data on the server.
+These **mutations** can be challenging to manage, as they require updates to the application's state and proper error handling.
+Actions simplify managing data mutations.
-## What are actions?
+Actions provide several benefits:
-Actions are asynchronous processing functions that allow you to submit data to your server and receive a response.
-They are isomorphic, meaning they can run either on the server or the client, depending on what is needed.
-This flexibility makes actions a powerful tool for managing and tracking data submissions.
+- **Integrated state management:**
+ Solid Router automatically tracks the execution state of an action, simplifying reactive UI feedback.
+- **Automatic data revalidation:**
+ After an action successfully completes, Solid Router revalidates relevant [`queries`](/solid-router/concepts/queries), ensuring the UI reflects the latest data.
+- **Progressive enhancement:**
+ When used with HTML forms, actions enable functionality even if JavaScript is not yet loaded.
-### How actions work
+## Defining actions
-Actions represent the server-side part of an [HTML form](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form).
-They handle submissions through POST requests, allowing you to easily use HTML forms to send data.
+Actions are defined by wrapping the data-mutation logic with the [`action` function](/solid-router/reference/data-apis/action).
-When a user performs an action, such as submitting a form, the data is sent to the server for processing via an action.
+```tsx
+import { action } from "@solidjs/router";
+
+const createTicketAction = action(async (subject: string) => {
+ const response = await fetch("https://my-api.com/support/tickets", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ subject }),
+ });
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ return { ok: false, message: errorData.message };
+ }
+
+ return { ok: true };
+}, "createTicket");
+```
+
+In this example, an action is defined that creates a support ticket using a remote API.
+
+## Using actions
+
+Actions can be triggered in two ways: using a HTML [`
+ );
+}
```
-In this example, the `echo` action simulates a fetch call with a 1 second delay before logging the message to the console.
-The `echo` action will act as a backend, however, it can be substituted for any API provided it can be run on the client.
-Typically, route actions are used with some sort of solution like fetch or GraphQL.
+In this example, when the form is submitted, `submitFeedbackAction` will be triggered with the `FormData` containing the form values.
+
+:::tip[Uploading files]
+If a form that includes file inputs, the `` element must have `enctype="multipart/form-data"` to correctly send the file data.
+
+```tsx
+
+
+ Upload
+
+```
-:::tip
-In [SolidStart](/solid-start) apps, it's recommended to use the [`"use server"`](/solid-start/reference/server/use-server) directive to leverage server-side functionality.
:::
-### Using actions
+#### Passing additional arguments
-To use the action, you can call it from within a component using [`useAction`](/solid-router/reference/data-apis/use-action).
-This returns a function that can be called with the necessary arguments to trigger the action.
+Sometimes, an action needs data that isn't part of the form's inputs.
+These additional arguments can be passed using the `with` method.
-```tsx del={1} ins={2,9-13}
+The `with` method creates a new action that wraps around the original action.
+When this new action is triggered, it forwards the arguments specified in the `with` method to the original action, followed by the `FormData` object.
+
+```tsx
import { action } from "@solidjs/router";
+
+const updateProductAction = action(
+ async (productId: string, formData: FormData) => {
+ // ... Sends the updated fields to the server.
+
+ return { ok: true };
+ },
+ "updateProduct"
+);
+
+function EditProductForm(props: { productId: string }) {
+ return (
+
+
+ Save
+
+ );
+}
+```
+
+In this example, `updateProductAction` receives `productId` (passed via `with`), and then the `formData` from the form.
+
+### With the `useAction` primitive
+
+For scenarios where a `` element is not suitable, the `useAction` primitive provides a way to trigger an action programmatically.
+The `useAction` primitive takes an action as its parameter and returns a function that, when called, triggers the action with the provided arguments.
+
+This approach requires client-side JavaScript and is not progressively enhanceable.
+
+```tsx
import { action, useAction } from "@solidjs/router";
-const echo = action(async (message: string) => {
- await new Promise((resolve, reject) => setTimeout(resolve, 1000));
- console.log(message);
+const markNotificationReadAction = action(async (notificationId: string) => {
+ // ... Marks a notification as read on the server.
});
-export function MyComponent() {
- const myEcho = useAction(echo);
+function NotificationItem(props: { id: string }) {
+ const markRead = useAction(markNotificationReadAction);
- myEcho("Hello from Solid!");
+ return markRead(props.id)}>Mark as read ;
}
```
-In this component, `useAction` is used to get a reference to the `echo` action.
-The action is then called with the message `"Hello from Solid!"`, which will be logged to the console after a 1 second delay.
+In this example, `markRead` is a function that can be called with arguments matching `markNotificationReadAction`.
+When the button is clicked, the action is triggered with the provided arguments.
-### Returning data from actions
+## Tracking submission state
-In many cases, after submitting data, the server sends some data back as well.
-This may be in the form of an error message if something has failed or the results of a successful operation.
-Anything returned from an action can be accessed using the reactive `action.result` property, where the value can change each time you submit your action.
+When an action is triggered, it creates a **submission** object.
+This object is a snapshot of the action's execution, containing its input, current status (pending or complete), and its final result or error.
+To access this state, Solid Router provides the [`useSubmission`](/solid-router/reference/data-apis/use-submission) and [`useSubmissions`](/solid-router/reference/data-apis/use-submissions) primitives.
-To access the action's result, you must pass the action to `useSubmission`:
+The `useSubmission` primitive tracks the state of the _most recent_ submission for a specific action.
+This is ideal for most use cases, such as disabling a form's submit button while the action is pending or displaying a confirmation message upon success.
-```tsx del={1} ins={2,11,15-17}
-import { action, useAction } from "@solidjs/router";
-import { action, useAction, useSubmission } from "@solidjs/router";
+```tsx
+import { Show } from "solid-js";
+import { action, useSubmission } from "@solidjs/router";
-const echo = action(async (message: string) => {
- await new Promise((resolve, reject) => setTimeout(resolve, 1000));
- return message;
-});
+const updateSettingsAction = action(async (formData: FormData) => {
+ // ... Sends the settings data to the server.
+}, "updateSettings");
-export function MyComponent() {
- const myEcho = useAction(echo);
- const echoing = useSubmission(echo);
+function UserSettingsForm() {
+ const submission = useSubmission(updateSettingsAction);
- myEcho("Hello from solid!");
+ return (
+
+
- setTimeout(() => myEcho("This is a second submission!"), 1500);
+
+ {submission.pending ? "Saving..." : "Save settings"}
+
+
+ );
+}
+```
- return {echoing.result}
;
+In this example, the form's submit button is disabled while `submission.pending` is `true`.
+
+:::tip
+To track multiple submissions for a single action, such as in a multi-file uploader interface, the [`useSubmissions` primitive](/solid-router/reference/data-apis/use-submissions) can be used.
+:::
+
+## Handling errors
+
+An action can fail for various reasons.
+A robust application must handle these failures gracefully.
+Solid Router provides two mechanisms for an action to signal failure: throwing an `Error` or returning a value.
+
+Throwing an `Error` is a valid way to signal failure.
+Solid Router will catch the thrown error and make it available in the `submission.error` property.
+However, this approach has some drawbacks.
+The `submission.error` property is typed as `any`, which undermines type safety in the consuming component.
+It is also difficult to convey structured error information, such as validation messages for multiple form fields, using a simple `Error` instance.
+
+For these reasons, the recommended practice is to always `return` a descriptive object from an action to represent its outcome.
+The returned object is available in the `submission.result` property, which will be fully typed.
+This makes handling different outcomes in the UI simple and safe.
+
+```tsx
+import { Show } from "solid-js";
+import { action, useSubmission } from "@solidjs/router";
+
+const verifyTwoFactorAction = action(async (formData: FormData) => {
+ const code = formData.get("code")?.toString();
+
+ if (!code || code.length !== 6) {
+ return {
+ ok: false,
+ errors: { code: "Enter the 6-digit code from the authenticator app." },
+ };
+ }
+
+ // ... Verifies the code with the server and handles potential errors.
+
+ return { ok: true };
+}, "verifyTwoFactor");
+
+function TwoFactorForm() {
+ const submission = useSubmission(verifyTwoFactorAction);
+
+ const errors = () => {
+ const result = submission.result;
+ if (result && !result.ok) {
+ return result.errors;
+ }
+ };
+
+ return (
+
+
+
+
+ {errors().code}
+
+
+
+
+ {submission.pending ? "Verifying..." : "Verify"}
+
+
+ );
+}
+```
+
+In this example, the `errors` derived signal inspects `submission.result` to check for failures.
+If an `errors` object is found, its properties are used to conditionally render error messages next to the relevant form fields.
+
+:::caution[Always return a value]
+It is important that an action consistently returns a value from all of its possible code paths.
+Because, if an action returns `undefined` or `null`, Solid Router removes that submission from its internal list upon completion.
+This can lead to unexpected behavior.
+
+For example, consider an action that returns an error object on failure but returns nothing on success.
+If the action fails once, `useSubmission` will correctly report the error.
+However, if a subsequent submission succeeds, it will be removed from the list, and `useSubmission` will continue to report the previous stale error state.
+To prevent this, ensure every code path in an action returns a value, such as `{ ok: true }` to indicate a successful outcome.
+:::
+
+## Automatic data revalidation
+
+After server data changes, the application's can become stale.
+To solve this, Solid Router automatically revalidates all [queries](/solid-router/concepts/queries) used in the same page after a successful action.
+This ensures any component using that data is automatically updated with the freshest information.
+
+For example, if a page displays a list of registered devices and includes a form to register a new one, the list will automatically update after the form is submitted.
+
+```tsx
+import { For } from "solid-js";
+import { query, action, createAsync } from "@solidjs/router";
+
+const getDevicesQuery = query(async () => {
+ // ... Fetches the list of registered devices.
+}, "devices");
+
+const registerDeviceAction = action(async (formData: FormData) => {
+ // ... Registers a new device on the server.
+}, "registerDevice");
+
+function DevicesPage() {
+ // This query will automatically revalidate after registerDeviceAction completes.
+ const devices = createAsync(() => getDevicesQuery());
+
+ return (
+
+
Registered devices
+
{(device) => {device.name}
}
+
+
Register new device
+
+
+ Register device
+
+
+ );
}
```
-Using `useSubmission` leaves the implementation details of how you trigger `echo` up to you.
-When handling user inputs, for example, it is better to use a `form` for a multitude of reasons.
+While this automatic behavior is convenient for most cases, more fine-grained control may be needed.
+The next section explains how to customize or even disable this behavior for specific actions.
-## Using forms to submit data
+## Managing navigation and revalidation
-When submitting data with actions, it is recommended to use HTML forms.
-These forms can be used prior to JavaScript loading, which creates instantly interactive applications.
-This also inherently provides accessibility benefits, saving the time of designing a custom UI library that may not have these benefits.
+While automatic revalidation is powerful, more control is often needed.
+It may be desirable to redirect the user to a different page, prevent revalidation entirely, or revalidate a specific set of queries.
+This is where response helpers come in.
-When using forms to submit actions, the first argument passed to your action function is an instance of [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData).
-To use actions with forms, pass the action to the `action` property of your form.
-This creates progressively enhanced forms that work even when JavaScript is disabled.
+Response helpers are functions that create special [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects.
+When an action returns or throws one of these responses, Solid Router intercepts it and performs a specific task.
-If you do not return a `Response` from your action, the user will stay on the same page and responses will be re-triggered.
-Using a `redirect` can tell the browser to navigate to a new page.
+### Redirecting
+To navigate the user to a new page after an action completes, the [`redirect` helper](/solid-router/reference/response-helpers/redirect) can be used.
+It can also be used to revalidate specific queries upon redirection, which is useful for updating data that is displayed on the new page.
```tsx
import { action, redirect } from "@solidjs/router";
+import { useSession } from "vinxi/http";
+
+const logoutAction = action(async () => {
+ "use server";
+ const session = await useSession({
+ password: process.env.SESSION_SECRET as string,
+ name: "session",
+ });
+
+ if (session.data.sessionId) {
+ await session.clear();
+ await db.session.delete({ id: sessionId });
+ }
+
+ throw redirect("/");
+}, "logout");
+```
+
+In this example, after a successful login, the `redirect` helper is used to navigate to the dashboard.
+It also revalidates the "session" query to ensure the UI reflects the user's authenticated state.
+
+### Customizing revalidation
-const isAdmin = action(async (formData: FormData) => {
- await new Promise((resolve, reject) => setTimeout(resolve, 1000));
+To override the default revalidation behavior, the [`reload`](/solid-router/reference/response-helpers/reload) and [`json`](/solid-router/reference/response-helpers/json) helpers can be used.
- const username = formData.get("username");
+- `reload` is used when only revalidation needs to be customized.
+- `json` is used when revalidation needs to be controlled _and_ data needs to be returned from the action.
- if (username === "admin") throw redirect("/admin");
- return new Error("Invalid username");
+Both helpers accept a `revalidate` option, which takes an array of query keys to revalidate.
+If an empty array (`[]`) is provided, revalidation is prevented altogether.
+
+```tsx
+import { action, reload, json } from "@solidjs/router";
+
+// Example 1: Revalidating a specific query
+const savePreferencesAction = action(async () => {
+ // ... Saves the user preferences.
+
+ // Only revalidate the 'userPreferences' query
+ throw reload({ revalidate: ["userPreferences"] });
});
-export function MyComponent() {
+// Example 2: Disabling revalidation and returning data
+const logActivityAction = action(async () => {
+ // ... Logs the activity to the server.
- return (
-
- Username:
-
-
-
- );
-}
+ // Return without revalidating any queries
+ return json({ ok: true }, { revalidate: [] });
+});
```
-**Note:** If you are uploading files make sure you include `enctype="multipart/form-data"` to your `` element.
+:::tip[Throwing vs. Returning]
+A response helper can be either `return`ed or `throw`n.
+In TypeScript, `throw` can be more convenient, as it avoids potential type conflicts with an action's expected return value.
+:::
+
+## Optimistic UI
+
+Optimistic UI is a pattern where the user interface is updated immediately after a user performs an operation.
+This is done without waiting for the server to confirm the operation's success.
+This approach makes an application feel faster and more responsive.
+
+Actions can be combined with local state management to implement optimistic UI.
+The `useSubmission` primitive can be used to access the input of an action as it's being submitted.
+This input can be used to temporarily update the UI.
-## Error handling
+```tsx
+import { For, Show } from "solid-js";
+import { query, action, createAsync, useSubmission } from "@solidjs/router";
+
+const getCartQuery = query(async () => {
+ // ... Fetches the current shopping cart items.
+}, "cart");
+
+const addToCartAction = action(async (formData: FormData) => {
+ // ... Adds a product to the cart.
+}, "addToCart");
+
+function CartPage() {
+ const cart = createAsync(() => getCartQuery());
+ const submission = useSubmission(addToCartAction);
+
+ const optimisticCart = () => {
+ const originalItems = cart() ?? [];
+ if (submission.pending) {
+ const formData = submission.input[0] as FormData;
+ const productId = formData.get("productId")?.toString();
+ const name = formData.get("name")?.toString();
+ if (productId && name) {
+ // Add the optimistic line item with a temporary identifier.
+ return [...originalItems, { id: "temp", productId, name, quantity: 1 }];
+ }
+ }
+ return originalItems;
+ };
+
+ return (
+
+
Your cart
+
{(item) => {item.name}
}
+
+
Add item
+
+
+
+
+ {submission.pending ? "Adding..." : "Add to cart"}
+
+
+
+ );
+}
+```
-Rather than throwing errors, it is recommended to return them from actions.
-This helps with the typing of submissions that would be used with `useSubmission`.
-This is important when handling progressive enhancement where no JavaScript is present in the client, so that errors can be used declaratively to render the updated page on the server.
+In this example, a derived signal `optimisticCart` is created.
+When an action is pending, it checks the `submission.input` and adds the new cart item to the list with a temporary ID.
+If the action fails, `submission.pending` becomes false, and `optimisticCart` will revert to showing the original list from `cart`.
+When the action succeeds, Solid Router automatically revalidates `getCartQuery` and updates the UI with the confirmed cart state.
-Additionally, when using server actions, it is good practice to handle errors on the server to sanitize error messages.
+:::note
+For more advanced patterns, consider using [TanStack Query](https://tanstack.com/query/latest/docs/framework/solid/guides/optimistic-updates).
+It provides robust tools for managing server state, including cache-based optimistic updates.
+:::
diff --git a/src/routes/solid-router/concepts/data.json b/src/routes/solid-router/concepts/data.json
index 9d91d50dd..835a9fb96 100644
--- a/src/routes/solid-router/concepts/data.json
+++ b/src/routes/solid-router/concepts/data.json
@@ -8,6 +8,7 @@
"nesting.mdx",
"layouts.mdx",
"alternative-routers.mdx",
+ "queries.mdx",
"actions.mdx"
]
-}
\ No newline at end of file
+}
diff --git a/src/routes/solid-router/data-fetching/data.json b/src/routes/solid-router/data-fetching/data.json
new file mode 100644
index 000000000..e67f9ab6e
--- /dev/null
+++ b/src/routes/solid-router/data-fetching/data.json
@@ -0,0 +1,4 @@
+{
+ "title": "Data fetching",
+ "pages": ["queries.mdx", "streaming.mdx", "revalidation.mdx", "how-to"]
+}
diff --git a/src/routes/solid-router/data-fetching/how-to/data.json b/src/routes/solid-router/data-fetching/how-to/data.json
new file mode 100644
index 000000000..d96966227
--- /dev/null
+++ b/src/routes/solid-router/data-fetching/how-to/data.json
@@ -0,0 +1,4 @@
+{
+ "title": "How to",
+ "pages": ["preload-data.mdx", "handle-error-and-loading-states.mdx"]
+}
diff --git a/src/routes/solid-router/data-fetching/how-to/handle-error-and-loading-states.mdx b/src/routes/solid-router/data-fetching/how-to/handle-error-and-loading-states.mdx
new file mode 100644
index 000000000..2f559a617
--- /dev/null
+++ b/src/routes/solid-router/data-fetching/how-to/handle-error-and-loading-states.mdx
@@ -0,0 +1,29 @@
+---
+title: "Handle pending and error states"
+---
+
+The `createAsync` primitive is designed to work with Solid's native components for managing asynchronous states.
+It reports its pending state to the nearest [`` boundary](/reference/components/suspense) to display loading fallbacks, and propagate errors to an [``](/reference/components/error-boundary) for handling and displaying error messages.
+
+```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 (
+ Could not fetch news.}>
+ Loading news...}>
+
+ {(item) => {item.headline} }
+
+
+
+ );
+}
+```
diff --git a/src/routes/solid-router/data-fetching/how-to/preload-data.mdx b/src/routes/solid-router/data-fetching/how-to/preload-data.mdx
new file mode 100644
index 000000000..39215abe6
--- /dev/null
+++ b/src/routes/solid-router/data-fetching/how-to/preload-data.mdx
@@ -0,0 +1,54 @@
+---
+title: "Preload data"
+---
+
+Preloading data improves perceived performance by fetching the data for an upcoming page before the user navigates to it.
+
+Solid Router initiates preloading in two scenarios:
+
+- When a user indicates intent to navigate to the page (e.g., by hovering over a link).
+- When the route's component is rendering.
+
+This ensures data fetching starts as early as possible, often making data ready once the component renders.
+
+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 queries execute and their results are stored in a short-lived internal cache.
+Once the user navigates and the destination route’s component renders, any `createAsync` calls within the page will consume the preloaded data.
+Thanks to the [deduplication mechanism](#deduplication), no redundant network requests are made.
+
+```tsx {18-20,27}
+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 (
+
+ {product().name}
+
+ );
+}
+
+function preloadProduct({ params }: { params: { id: string } }) {
+ getProductQuery(params.id);
+}
+
+function Routes() {
+ return (
+
+ );
+}
+```
+
+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.
diff --git a/src/routes/solid-router/data-fetching/queries.mdx b/src/routes/solid-router/data-fetching/queries.mdx
new file mode 100644
index 000000000..0f43335a6
--- /dev/null
+++ b/src/routes/solid-router/data-fetching/queries.mdx
@@ -0,0 +1,81 @@
+---
+title: "Queries"
+---
+
+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, the defined query fetches a user's profile from an API.
+If the request fails, the fetcher will throw an error that will be caught by the nearest [``](/reference/components/error-boundary) in the component tree.
+
+## Using queries in components
+
+Defining a query does not by itself fetch any data.
+To access its data, the query can be used with the [`createAsync` primitive](/solid-router/reference/data-apis/create-async).
+`createAsync` takes an asynchronous function, such as a query, and returns a signal that tracks its result.
+
+```tsx
+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 (
+
+ {(article) => {article.title}
}
+
+ );
+}
+```
+
+In this example, `createAsync` is used to call the query.
+Once the query completes, `articles` holds the result, which is then rendered.
+
+:::tip
+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.
+It works like `createAsync`, but returns a [store](/concepts/stores) for easier state management..
+:::
+
+## Deduplication
+
+A key feature of queries is their ability to deduplicate requests, preventing redundant data fetching in quick succession.
+
+One common use case 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 completed, and the data is available instantly without triggering another network request.
+This mechanism is fundamental to the performance of Solid Router applications.
+
+Deduplication also applies when multiple components on the same page use the same query.
+As long as at least one component is actively using the query, Solid Router will reuse the cached result instead of refetching the data.
diff --git a/src/routes/solid-router/data-fetching/revalidation.mdx b/src/routes/solid-router/data-fetching/revalidation.mdx
new file mode 100644
index 000000000..3ee773fdf
--- /dev/null
+++ b/src/routes/solid-router/data-fetching/revalidation.mdx
@@ -0,0 +1,60 @@
+---
+title: "Revalidation"
+---
+
+Since server data can change, Solid Router provides mechanisms to revalidate queries and keep the UI up to date.
+
+The most common case is **automatic revalidation**.
+After an [action](/solid-router/concepts/actions) completes successfully, Solid Router automatically revalidates all active queries on the page.
+For more details, see the [actions documentation](/solid-router/concepts/actions#automatic-data-revalidation).
+
+For more fine-grained control, you can trigger revalidation manually with the [`revalidate` function](/solid-router/reference/data-apis/revalidate).
+It accepts a query key (or an array of keys) to target specific queries.
+Each query exposes two properties for this: `key` and `keyFor`.
+
+- `query.key` is the base key for a query and targets all of its instances.
+ Using this key will revalidate all data fetched by that query, regardless of the arguments provided.
+- `query.keyFor(arguments)` generates a key for a specific set of arguments, allowing you to target and revalidate only that particular query.
+
+```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 (
+
+
Refetch all tasks
+
{(project) => }
+
+ );
+}
+
+function Project(props: { id: string }) {
+ const tasks = createAsync(() => getProjectTasksQuery(props.id));
+
+ function refetchTasks() {
+ revalidate(getProjectTasksQuery.keyFor(props.id));
+ }
+
+ return (
+
+
Refetch tasks for this project
+
{(task) => {task.title}
}
+
+ );
+}
+```
diff --git a/src/routes/solid-router/data-fetching/streaming.mdx b/src/routes/solid-router/data-fetching/streaming.mdx
new file mode 100644
index 000000000..42fa45f41
--- /dev/null
+++ b/src/routes/solid-router/data-fetching/streaming.mdx
@@ -0,0 +1,90 @@
+---
+title: "Streaming"
+---
+
+In traditional server-rendered applications, the server must fetch all data before rendering and sending the page to the browser.
+If some queries are slow, this delays the initial load.
+**Streaming** solves this by sending the page’s HTML shell immediately and progressively streaming data-dependent sections as they become ready.
+
+When a query is accessed during a server-side render, Solid suspends the UI until the data resolves.
+By default, this suspension affects the entire page.
+
+To control this behavior, you can use suspense boundaries - regions of the component tree defined by a [`` component](/reference/components/suspense).
+It isolates asynchronous behavior to a specific section of the page.
+
+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.
+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.
+
+```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 (
+
+
Dashboard
+
Loading account stats...}>
+
+ {(stat) => (
+
+ {stat.label}: {stat.value}
+
+ )}
+
+
+
+
Loading recent transactions...}>
+
+ {(transaction) => (
+
+ {transaction.description} - {transaction.amount}
+
+ )}
+
+
+
+ );
+}
+```
+
+For example, each `` component creates its own independent boundary.
+The server can stream the heading `Dashboard ` immediately, while the `stats` and `transactions` are handled separately.
+If the `transactions` query is slow, only its boundary will display a fallback, while `stats` will render as soon as its data is ready.
+
+## When to disable streaming
+
+While streaming is powerful, there are cases where it is better to wait for the data to load on the server.
+In these situations, you can use the `deferStream` option in `createAsync`.
+
+When `deferStream` is set to `true`, the server waits for the query to resolve before sending the initial HTML.
+
+A common reason to disable streaming is for Search Engine Optimization (SEO).
+Some search engine crawlers may not wait for streamed content to load.
+If critical data, such as a page title or meta description, affects SEO, it should be included in the initial server response.
+
+```tsx
+import { query, createAsync } from "@solidjs/router";
+
+const getArticleQuery = query(async () => {
+ // ... Fetches an article.
+}, "article");
+
+function ArticleHeader() {
+ const article = createAsync(() => getArticleQuery(), {
+ deferStream: true,
+ });
+
+ return {article()?.title} ;
+}
+```
diff --git a/src/routes/solid-router/data.json b/src/routes/solid-router/data.json
index a141ae4d4..e60a784f2 100644
--- a/src/routes/solid-router/data.json
+++ b/src/routes/solid-router/data.json
@@ -5,6 +5,7 @@
"getting-started",
"concepts",
"rendering-modes",
+ "data-fetching",
"advanced-concepts",
"guides"
]
diff --git a/src/routes/solid-router/reference/data-apis/action.mdx b/src/routes/solid-router/reference/data-apis/action.mdx
index 8f3a5e666..7847080fc 100644
--- a/src/routes/solid-router/reference/data-apis/action.mdx
+++ b/src/routes/solid-router/reference/data-apis/action.mdx
@@ -2,196 +2,139 @@
title: action
---
-Actions are data mutations that can trigger invalidations and further routing.
-A list of prebuilt response helpers can be found below.
+The `action` function wraps an asynchronous function and returns an [action](/solid-router/concepts/actions).
-```jsx
-import { action, revalidate, redirect } from "@solidjs/router"
+When an action is triggered, it creates a submission object that tracks its execution status.
+This state can be accessed with the [`useSubmission`](/solid-router/reference/data-apis/use-submission) and [`useSubmissions`](/solid-router/reference/data-apis/use-submissions) primitives.
-// anywhere
-const myAction = action(async (data) => {
- await doMutation(data);
- throw redirect("/", { revalidate: getUser.keyFor(data.id) }); // throw a response to do a redirect
-});
-
-// in component
-
+After an action completed successfully, all queries active in the same page are revalidated, unless revalidation is configured using [response helpers](/solid-router/concepts/actions#managing-navigation-and-revalidation).
-//or
-
+## Import
+```tsx
+import { action } from "@solidjs/router";
```
-Actions only work with **post** requests.
-This means forms require `method="post"`.
-
-A `with` method can be used when typed data is required.
-This removes the need to use `FormData` or other additional hidden fields.
-The `with` method works similar to [`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind), which applies the arguments in order.
-
-Without `with`:
-
-```jsx
-const deleteTodo = action(async (formData: FormData) => {
- const id = Number(formData.get("id"))
- await api.deleteTodo(id)
-})
-
-
-
- Delete
-
-
+## Type
+
+```tsx
+function action, U = void>(
+ fn: (...args: T) => Promise,
+ name?: string
+): Action;
+
+function action, U = void>(
+ fn: (...args: T) => Promise,
+ options?: { name?: string; onComplete?: (s: Submission) => void }
+): Action;
+
+function action, U = void>(
+ fn: (...args: T) => Promise,
+ options:
+ | string
+ | { name?: string; onComplete?: (s: Submission) => void } = {}
+): Action;
```
-Using `with`:
+## Parameters
-```jsx del={5,6} ins={7}
-const deleteTodo = action(async (id: number) => {
- await api.deleteTodo(id)
-})
+### `handler`
-
-
-
- Delete
-
+- **Type:** `(...args: T) => Promise`
+- **Required:** Yes
-```
+An asynchronous function that performs the action's logic.
+When the action is used with a [`` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form), the function receives a [`FormData` object](https://developer.mozilla.org/en-US/docs/Web/API/FormData) as its first parameter.
-:::tip
-In [SolidStart](/solid-start) apps, it's recommended to use the [`"use server"`](/solid-start/reference/server/use-server) directive to leverage server-side caching.
-:::
+### `options`
-## Notes of ` ` implementation and SSR
+- **Type:** `string | { name?: string; onComplete?: (s: Submission) => void }`
+- **Required**: No
-This requires stable references because a string can only be serialized as an attribute, and it is crucial for consistency across SSR. where these references must align.
-The solution is to provide a unique name.
+A unique string used to identify the action in Server-Side Rendering (SSR) environments.
+This is required when using the action with a `` element.
-```jsx
-const myAction = action(async (args) => {}, "my-action");
-```
+Alternatively, a configuration object can be passed with the following properties.
-## `useAction`
+#### `name`
-Instead of forms, actions can directly be wrapped in a `useAction` primitive.
-This is how router context is created.
+- **Type:** `string`
+- **Required:** No
-```jsx
-// in component
-const submit = useAction(myAction);
-submit(...args);
+The unique string to identify the action in SSR environments.
+Required for ` ` usage.
-```
+#### `onComplete` (optional):
-The outside of a form context can use custom data instead of `formData`.
-These helpers preserve types.
+- **Type:** `(s: Submission) => void`
+- **Required:** No
-However, even when used with server functions, such as with [SolidStart](https://start.solidjs.com/getting-started/what-is-solidstart), this requires client-side JavaScript and is not progressively enhanceable like forms are.
+A function that runs after the action completes.
+It receives a submission object as its parameter.
-## `useSubmission`/`useSubmissions`
+## Return value
-These functions are used when incorporating optimistic updates during ongoing actions.
-They provide either a singular Submission (the latest one), or a collection of Submissions that match, with an optional filtering function.
+`action` returns an action with the following properties.
-```jsx
-type Submission = {
- input: T;
- result: U;
- error: any;
- pending: boolean
- clear: () => {}
- retry: () => {}
-}
+### `with`
-const submissions = useSubmissions(action, (input) => filter(input));
-const submission = useSubmission(action, (input) => filter(input));
+A method that wraps the original action and returns a new one.
+When this new action is triggered, the arguments passed to `with` are forwarded to the original action.
+If this new action is used with a ``, the `FormData` object is passed as the last parameter, after any other forwarded parameters.
-```
-
-## Revalidate cached functions
+## Examples
-### Revalidate all (default)
-By default all cached functions will be revalidated wether the action does not return or return a "normal" response.
+### Form submission
-```jsx
+```tsx
+import { action } from "@solidjs/router";
-const deleteTodo = action(async (formData: FormData) => {
- const id = Number(formData.get("id"))
- await api.deleteTodo(id)
- // ...
- return new Response("success", { status: 200 });
-})
-```
-
-### Revalidate specific cached keys
-
-By returning a response with solid-router's `json`, `reload` or `redirect` helpers you can pass a key / keys to the revalidate prop as the second argument of the json response helper.
-You can either pass as `string` directly or use the cached functions `key` or `keyFor` props.
-
-```jsx
-import { action, json, reload, redirect } from "@solidjs/router"
-
-const deleteTodo = action(async (formData: FormData) => {
- const id = Number(formData.get("id"))
- await api.deleteTodo(id)
- return json(
- { deleted: id },
- { revalidate: ["getAllTodos", getTodos.key, getTodoByID.keyFor(id)]}
- )
-
- //or
- return reload({ revalidate: ["getAllTodos", getTodos.key, getTodoByID.keyFor(id)]})
-
- //or
- return redirect("/", { revalidate: ["getAllTodos", getTodos.key, getTodoByID.keyFor(id)]})
-
-})
+const addTodoAction = action(async (formData: FormData) => {
+ // Adds a new todo to the server.
+}, "addTodo");
+function TodoForm() {
+ return (
+
+
+ Add todo
+
+ );
+}
```
-## Prevent revalidation
-To opt out of any revalidation you can pass any `string` to revalidate which is not a key of any cached function.
-
-```jsx
-const deleteTodo = action(async (formData: FormData) => {
- const id = Number(formData.get("id"))
- await api.deleteTodo(id)
- // returns a `json` without revalidating the action.
- return json(`deleted ${id}`,{ revalidate: "nothing" })
+### Passing additional arguments
- // or reload the route without revalidating the request.
- return reload({ revalidate: "nothing" })
+```tsx
+import { action } from "@solidjs/router";
- // or redirect without revalidating
- return redirect("/", { revalidate: "nothing" })
-})
+const addTodoAction = action(async (userId: number, formData: FormData) => {
+ // ... Adds a new todo for a specific user.
+}, "addTodo");
+function TodoForm() {
+ const userId = 1;
+ return (
+
+
+ Add todo
+
+ );
+}
```
-### Revalidate without action
+### Triggering actions programmatically
-Cached functions can also be revalidated by the `revalidate` helper.
+```tsx
+import { action, useAction } from "@solidjs/router";
-```jsx
-revalidate([getTodos.key, getTodoByID.keyFor(id)])
+const markDoneAction = action(async (id: string) => {
+ // ... Marks a todo as done on the server.
+});
-```
+function NotificationItem(props: { id: string }) {
+ const markDone = useAction(markDoneAction);
-This is also great if you want to set your own "refresh" interval e.g. poll data every 30 seconds.
-
-```jsx
-export default function TodoLayout(){
-
- const todos = createAsync(() => getTodos())
-
- onMount(() => {
- //30 second polling
- const interval = setInterval(() => revalidate(getTodos.key),1000 * 30)
- onCleanup(() => clearInterval(interval))
- })
-
- // ...
+ return markDone(props.id)}>Mark as done ;
}
-
```
diff --git a/src/routes/solid-router/reference/data-apis/create-async-store.mdx b/src/routes/solid-router/reference/data-apis/create-async-store.mdx
index a1659422c..caa015890 100644
--- a/src/routes/solid-router/reference/data-apis/create-async-store.mdx
+++ b/src/routes/solid-router/reference/data-apis/create-async-store.mdx
@@ -2,8 +2,128 @@
title: createAsyncStore
---
-Similar to [createAsync](/solid-router/reference/data-apis/create-async) except it uses a deeply reactive store. Perfect for applying fine-grained changes to large model data that updates.
+The `createAsyncStore` primitive manages asynchronous data fetching by tracking the result of a promise-returning function in a [store](/concepts/stores).
-```jsx
-const todos = createAsyncStore(() => getTodos());
+The main difference from [createAsync](/solid-router/reference/data-apis/create-async) is its use of reconciliation: when new data arrives, it intelligently merges with the existing store, updating only changed fields while preserving unchanged state.
+
+## Import
+
+```tsx
+import { createAsyncStore } from "@solidjs/router";
+```
+
+## Type
+
+```tsx
+function createAsyncStore(
+ fn: (prev: T) => Promise,
+ options: {
+ name?: string;
+ initialValue: T;
+ deferStream?: boolean;
+ reconcile?: ReconcileOptions;
+ }
+): AccessorWithLatest;
+
+function createAsyncStore(
+ fn: (prev: T | undefined) => Promise,
+ options?: {
+ name?: string;
+ initialValue?: T;
+ deferStream?: boolean;
+ reconcile?: ReconcileOptions;
+ }
+): AccessorWithLatest;
+```
+
+## Parameters
+
+### `fetcher`
+
+- **Type:** `(prev: T | undefined) => Promise`
+- **Required:** Yes
+
+An asynchronous function or a function that returns a `Promise`.
+The resolved value of this `Promise` is what `createAsyncStore` tracks.
+This function is reactive and will automatically re-execute if any of its dependencies change.
+
+### `options`
+
+- **Type:** `{ name?: string; initialValue?: T; deferStream?: boolean; reconcile?: ReconcileOptions; }`
+- **Required:** No
+
+An object for configuring the primitive.
+It has the following properties.
+
+#### `name`
+
+- **Type:** `string`
+- **Required:** No
+
+A name for the resource, used for identification in debugging tools like [Solid Debugger](https://github.com/thetarnav/solid-devtools).
+
+#### `initialValue`
+
+- **Type:** `T`
+- **Required:** No
+
+The initial value of the returned store before the fetcher resolves.
+
+#### `deferStream`
+
+- **Type:** `boolean`
+- **Required:** No
+
+If `true`, [streaming](/solid-router/concepts/queries#streaming) will be deferred until the resource has resolved.
+
+#### `reconcile`
+
+- **Type:** `ReconcileOptions`
+- **Required:** No
+
+[Options](/reference/store-utilities/reconcile#options) passed directly to the `reconcile` function.
+It controls how new data is merged with the existing store.
+
+## Return value
+
+`createAsyncStore` returns a derived signal that contains the resolved value of the fetcher as a store.
+
+While the fetcher is executing for the first time, unless an `initialValue` is specified, the store's value is `undefined`.
+
+## Examples
+
+### Basic usage
+
+```tsx
+import { For, createSignal } from "solid-js";
+import { createAsyncStore, query } from "@solidjs/router";
+
+const getNotificationsQuery = query(async (unreadOnly: boolean) => {
+ // ... Fetches the list of notifications from the server.
+}, "notifications");
+
+function Notifications() {
+ const [unreadOnly, setUnreadOnly] = createSignal(false);
+ const notifications = createAsyncStore(() =>
+ getNotificationsQuery(unreadOnly())
+ );
+
+ return (
+
+
setUnreadOnly(!unreadOnly())}>
+ Toggle unread
+
+
+
+ {(notification) => (
+
+ {notification.message}
+ {notification.user.name}
+
+ )}
+
+
+
+ );
+}
```
diff --git a/src/routes/solid-router/reference/data-apis/create-async.mdx b/src/routes/solid-router/reference/data-apis/create-async.mdx
index c8b7b833d..59823bdc7 100644
--- a/src/routes/solid-router/reference/data-apis/create-async.mdx
+++ b/src/routes/solid-router/reference/data-apis/create-async.mdx
@@ -2,42 +2,125 @@
title: createAsync
---
-An asynchronous primitive with a function that tracks similar to `createMemo`.
-`createAsync` expects a promise back that is then turned into a Signal.
-Reading it before it is ready causes Suspense/Transitions to trigger.
+The `createAsync` primitive manages asynchronous data fetching by tracking the result of a promise-returning function.
-:::caution
- Using `query` in `createResource` directly will not work since the fetcher is
- not reactive. This means that it will not invalidate properly.
+:::note
+`createAsync` is currently a thin wrapper over `createResource`.
+While `createResource` offers similar functionality, **`createAsync` is the recommended primitive for most asynchronous data fetching**.
+It is intended to be the standard async primitive in a future Solid 2.0 release.
:::
-This is light wrapper over [`createResource`](/reference/basic-reactivity/create-resource) which serves as a stand-in for a future primitive being brought to Solid core in 2.0.
-It is recommended that `createAsync` be used in favor of `createResource` specially when in a **SolidStart** app because `createAsync` works better in conjunction with the [cache](/solid-router/reference/data-apis/cache) helper.
+## Import
+```tsx
+import { createAsync } from "@solidjs/router";
+```
+## Type
-```tsx title="component.tsx" {6,10}
-import { createAsync } from "@solidjs/router";
-import { Suspense } from "solid-js";
-import { getUser } from "./api";
+```tsx
+function createAsync(
+ fn: (prev: T) => Promise,
+ options: {
+ name?: string;
+ initialValue: T;
+ deferStream?: boolean;
+ }
+): AccessorWithLatest;
-export function Component () => {
- const user = createAsync(() => getUser(params.id));
+function createAsync(
+ fn: (prev: T | undefined) => Promise,
+ options?: {
+ name?: string;
+ initialValue?: T;
+ deferStream?: boolean;
+ }
+): AccessorWithLatest;
+```
- return (
-
- {user()}
-
- );
+## Parameters
+
+### `fetcher`
+
+- **Type:** `(prev: T | undefined) => Promise`
+- **Required:** Yes
+
+An asynchronous function or a function that returns a `Promise`.
+The resolved value of this `Promise` is what `createAsync` tracks.
+This function is reactive and will automatically re-execute if any of its dependencies change.
+
+### `options`
+
+- **Type:** `{ name?: string; initialValue?: T; deferStream?: boolean; }`
+- **Required:** No
+
+An object for configuring the primitive.
+It has the following properties:
+
+#### `name`
+
+- **Type:** `string`
+- **Required:** No
+
+A name for the resource, used for identification in debugging tools like [Solid Debugger](https://github.com/thetarnav/solid-devtools).
+
+#### `initialValue`
+
+- **Type:** `T`
+- **Required:** No
+
+The initial value of the returned signal before the fetcher finishes executing.
+
+#### `deferStream`
+
+- **Type:** `boolean`
+- **Required:** No
+
+If `true`, [streaming](/solid-router/concepts/queries#streaming) will be deferred until the fetcher finishes executing.
+
+## Return value
+
+`createAsync` returns a derived signal that contains the resolved value of the fetcher.
+
+While the fetcher is executing for the first time, unless an `initialValue` is specified, the signal's value is `undefined`.
+
+## Examples
+
+### Basic usage
+
+```tsx
+import { createAsync, query } from "@solidjs/router";
+
+const getUserQuery = query(async (id: string) => {
+ // ... Fetches the user from the server.
+}, "user");
+
+function UserProfile() {
+ const user = createAsync(() => getUserQuery());
+
+ return {user()?.name}
;
+}
```
-## Options
+### Handling loading and errors
+
+```tsx
+import { Suspense, ErrorBoundary, For } from "solid-js";
+import { createAsync, query } from "@solidjs/router";
+
+const getUserQuery = query(async (id: string) => {
+ // ... Fetches the user from the server.
+}, "user");
-| Name | Type | Default | Description |
-| ------------ | ----------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| name | `string` | `undefined` | A name for the resource. This is used for debugging purposes. |
-| deferStream | `boolean` | `false` | If true, Solid will wait for the resource to resolve before flushing the stream. |
-| initialValue | `any` | `undefined` | The initial value of the resource. |
-| onHydrated | `function` | `undefined` | A callback that is called when the resource is hydrated. |
-| ssrLoadFrom | `"server" \| "initial"` | `"server"` | The source of the initial value for SSR. If set to `"initial"`, the resource will use the `initialValue` option instead of the value returned by the fetcher. |
-| storage | `function` | `createSignal` | A function that returns a signal. This can be used to create a custom storage for the resource. This is still experimental
+function UserProfile() {
+ const user = createAsync(() => getUserQuery());
+
+ return (
+ Could not fetch user data.}>
+ Loading user...}>
+ {user()!.name}
+
+
+ );
+}
+```
diff --git a/src/routes/solid-router/reference/data-apis/query.mdx b/src/routes/solid-router/reference/data-apis/query.mdx
index eab7749ba..09c75eef5 100644
--- a/src/routes/solid-router/reference/data-apis/query.mdx
+++ b/src/routes/solid-router/reference/data-apis/query.mdx
@@ -2,118 +2,87 @@
title: query
---
-`query` is a [higher-order function](https://en.wikipedia.org/wiki/Higher-order_function) designed to create a new function with the same signature as the function passed to it.
-When this newly created function is called for the first time with a specific set of arguments, the original function is run, and its return value is stored in a cache and returned to the caller of the created function.
-The next time the created function is called with the same arguments (as long as the cache is still valid), it will return the cached value instead of re-executing the original function.
-
-:::note
-`query` can be defined anywhere and then used inside your components with [`createAsync`](/solid-router/reference/data-apis/create-async).
-
-However, using `query` directly in [`createResource`](/reference/basic-reactivity/create-resource) will not work since the fetcher is not reactive and will not invalidate properly.
-
-:::
-
-## Usage
-
-```js
-const getUser = query(
- (id, options = {}) =>
- fetch(`/api/users/${id}?summary=${options.summary || false}`).then((r) =>
- r.json()
- ),
- "usersById"
-);
-
-getUser(123); // Causes a GET request to /api/users/123?summary=false
-getUser(123); // Does not cause a GET request
-getUser(123, { summary: true }); // Causes a GET request to /api/users/123?summary=true
-setTimeout(() => getUser(123, { summary: true }), 999000); // Eventually causes another GET request to /api/users/123?summary=true
+The `query` function wraps an asynchronous function (the fetcher) and returns a query.
+
+The primary purpose of a query is to prevent redundant data fetching.
+When a query is called, a key is generated from its name and arguments.
+This key is used to internally cache the result of the fetcher.
+If a query with the same key is called, the cached result is used in these scenarios:
+
+- **For preloading:**
+ After a route's data is preloaded, a subsequent call to the same query within a 5-second window uses the preloaded data.
+- **For active subscriptions:**
+ When a query is actively being used by a component (e.g., via [`createAsync`](/solid-router/reference/data-apis/create-async)), its data is reused without expiration.
+- **On native history navigation:**
+ When navigating with the browser's back or forward buttons, the data is reused instead of being re-fetched.
+- **For server-side deduplication:**
+ Within a single server-side rendering (SSR) request, repeated calls to the same query reuse the same value.
+- **During client hydration:**
+ If SSR has provided data for a key, that data is used immediately on the client without a new network request.
+
+## Import
+
+```tsx
+import { query } from "@solidjs/router";
```
-### With preload functions
-
-Using it with a [preload function](/solid-router/reference/preload-functions/preload):
-
-```js
-import { lazy } from "solid-js";
-import { Route } from "@solidjs/router";
-import { getUser } from ... // the cache function
+## Type
-const User = lazy(() => import("./pages/users/[id].js"));
-
-// preload function
-function preloadUser({params, location}) {
- void getUser(params.id)
-}
-
-// Pass it in the route definition
- ;
+```tsx
+function query any>(
+ fn: T,
+ name: string
+): CachedFunction;
```
-### Inside a route's component
-
-Using it inside a route's component:
-
-```jsx
-// pages/users/[id].js
-import { getUser } from ... // the cache function
+## Parameters
-export default function User(props) {
- const user = createAsync(() => getUser(props.params.id));
- return {user().name} ;
-}
-```
+### `fetcher`
-## Query Function Capabilities
+- **Type:** `T extends (...args: any) => any`
+- **Required:** Yes
-`query` accomplishes the following:
+An asynchronous function that handles the logic for fetching data.
+All arguments passed to this function must be JSON-serializable.
-1. Deduping on the server for the lifetime of the request.
-2. It fills a preload cache in the browser - this lasts 5 seconds.
- When a route is preloaded on hover or when preload is called when entering a route it will make sure to dedupe calls.
-3. A reactive refetch mechanism based on key.
- This prevents routes that are not new from retriggering on action revalidation.
-4. Serve as a back/forward cache for browser navigation for up to 5 minutes.
- Any user based navigation or link click bypasses this cache.
- Revalidation or new fetch updates the cache.
+### `name`
-## Query Keys
+- **Type:** `string`
+- **Required:** Yes
-To ensure that the query keys are consistent and unique, arguments are deterministically serialized using `JSON.stringify`.
-Before serialization, key/value pairs in objects are sorted so that the order of properties does not affect the serialization.
-For instance, both `{ name: 'Ryan', awesome: true }` and `{ awesome: true, name: 'Ryan' }` will serialize to the same string so that they produce the same query key.
+A string used as a namespace for the query.
+Solid Router combines this with the query's arguments to generate a unique key for deduplication.
## Return value
-The return value is a `CachedFunction`, a function that has the same signature as the function you passed to `query`.
-This cached function stores the return value using the query key.
-Under most circumstances, this temporarily prevents the passed function from running with the same arguments, even if the created function is called repeatedly.
+`query` returns a new function with the same call signature as the fetcher.
+This returned function has the following properties attached to it:
-## Arguments
+### `key`
-| argument | type | description |
-| -------- | ----------------------- | ------------------------------------------------------------------------- |
-| `fn` | `(...args: any) => any` | A function whose return value you'd like to be cached. |
-| `name`\* | string | Any arbitrary string that you'd like to use as the rest of the query key. |
+The base key for the query, derived from its name.
-\*Since the internal cache is shared by all the functions using `query`, the string should be unique for each function passed to `query`.
-If the same key is used with multiple functions, one function might return the cached result of the other.
+### `keyFor`
-## Methods
+A function that takes the same arguments as the fetcher and returns a string representing a specific key for that set of arguments.
-### `.key` and `.keyFor`
+## Example
-Query functions provide `.key` and `.keyFor`, are useful when retrieving the keys used in cases involving invalidation:
+### Basic usage
-```ts
-let id = 5;
-getUser.key; // returns "users"
-getUser.keyFor(id); // returns "users[5]"
-```
+```tsx
+import { query } from "@solidjs/router";
-### `revalidate`
+const getUserProfileQuery = query(async (userId: string) => {
+ const response = await fetch(
+ `https://api.example.com/users/${encodeURIComponent(userId)}`
+ );
+ const json = await response.json();
-The `query` can be revalidated using the `revalidate` method or the `revalidate` keys that are set on the response from the actions.
-If the entire key is passed, it will invalidate all entries for the cache (ie. `users` in the example above).
-If only a single entry needs to be invalidated, `keyFor` is provided.
-To revalidate everything in the cache, pass `undefined` as the key.
+ if (!response.ok) {
+ throw new Error(json?.message ?? "Failed to load user profile.");
+ }
+
+ return json;
+}, "userProfile");
+```
diff --git a/src/routes/solid-router/reference/data-apis/use-action.mdx b/src/routes/solid-router/reference/data-apis/use-action.mdx
index 91d8a5e80..68e117b4b 100644
--- a/src/routes/solid-router/reference/data-apis/use-action.mdx
+++ b/src/routes/solid-router/reference/data-apis/use-action.mdx
@@ -2,26 +2,50 @@
title: useAction
---
-`useAction` allows an [`action`](/solid-router/reference/data-apis/action) to be invoked programmatically.
+The `useAction` primitive returns a function that triggers an [action](/solid-router/concepts/actions) when called.
+
+`useAction` requires client-side JavaScript and is not progressively enhanceable.
+
+## Import
```tsx
import { useAction } from "@solidjs/router";
-import { updateNameAction } from "./actions";
+```
-const updateName = useAction(updateNameAction);
+## Type
-const result = updateName("John Wick");
+```tsx
+function useAction, U, V>(
+ action: Action
+): (...args: Parameters>) => Promise>;
```
-:::note
-`useAction` requires client-side JavaScript and is not progressively enhanceable.
-:::
-
## Parameters
-- `action`: The action to be invoked.
+### `action`
+
+- **Type:** `Action`
+- **Required:** Yes
-## Returns
+The action to be triggered.
-`useAction` returns a function that invokes the action.
-It shares the same signature as the action itself.
+## Return value
+
+`useAction` returns a function that triggers the action.
+It takes the same parameters as the action handler and returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) that resolves with the action's result.
+
+## Example
+
+```tsx
+import { action, useAction } from "@solidjs/router";
+
+const likePostAction = action(async (id: string) => {
+ // ... Likes a post on the server.
+});
+
+function LikeButton(props: { postId: string }) {
+ const likePost = useAction(likePostAction);
+
+ return likePost(props.postId)}>Like ;
+}
+```
diff --git a/src/routes/solid-router/reference/data-apis/use-submission.mdx b/src/routes/solid-router/reference/data-apis/use-submission.mdx
index 7153e664c..acd2f82e9 100644
--- a/src/routes/solid-router/reference/data-apis/use-submission.mdx
+++ b/src/routes/solid-router/reference/data-apis/use-submission.mdx
@@ -2,160 +2,127 @@
title: useSubmission
---
-This helper is used to handle form submissions and can provide optimistic updates while actions are in flight as well as pending state feedback.
-This method will return a single (latest) value while its sibling, [`useSubmissions`](/solid-router/reference/data-apis/use-submissions), will return all values submitted while the component is active. With an optional second parameter for a filter function.
+The `useSubmission` primitive returns the state of the _most recent_ submission for a given [action](/solid-router/concepts/actions).
-It's important to note that `useSubmission` requires the form method to be **post** otherwise it will trigger a browser navigation and will not work.
+## Import
-```tsx title="component.tsx" {4,8}
+```tsx
import { useSubmission } from "@solidjs/router";
+```
-function Component() {
- const submission = useSubmission(postNameAction);
-
- return (
-
-
-
- {submission.pending ? "Adding..." : "Add"}
-
-
- )
-}
+## Type
+
+```tsx
+function useSubmission, U, V>(
+ fn: Action,
+ filter?: (input: V) => boolean
+): Submission> | SubmissionStub;
```
-:::note
-Learn more about actions in the [`action`](/solid-router/reference/data-apis/action) docs.
-:::
+## Parameters
-## Filtering Submissions
+### `action`
-As an optional second parameter, the `useSubmission` helper can receive a filter function to only return the submission that matches the condition.
-The filter receives the submitted dated as a parameter and should return a boolean value.
-E.g.: action below will only submit if the name is "solid".
+- **Type:** `Action`
+- **Required:** Yes
-```tsx title="component.tsx" {4-8}
-import { useSubmission } from "@solidjs/router";
+The action to track.
-function Component() {
- const submission = useSubmission(postNameAction, ([formData]) => {
- const name = formData.get("name") ?? "";
-
- return name === "solid";
- });
-
- return (
-
-
-
- {submission.pending ? "Adding..." : "Add"}
-
-
- )
-}
-```
+### `filter`
-## Optimistic Updates
+- **Type:** `(input: V) => boolean`
+- **Required:** No
-When the form is submitted, the `submission` object will be updated with the new value and the `pending` property will be set to `true`.
-This allows you to provide feedback to the user that the action is in progress.
-Once the action is complete, the `pending` property will be set to `false` and the `result` property will be updated with final value.
+A function that filters submissions.
+It is executed for each submission in the order of creation.
+It receives an array of the action's inputs as a parameter and must return `true` to select the submission or `false` otherwise.
+The first submission that passes the filter is returned by `useSubmission`.
-```tsx tab title="TypeScript" {6,10-12}
-// component.tsx
-import { Show } from "solid-js";
-import { useSubmission } from "@solidjs/router";
+## Return value
-function Component() {
- const submission = useSubmission(postNameAction);
-
- return (
- <>
-
- {(name) => Optimistic: {name() as string}
}
-
-
-
- {(name) => Result: {name()}
}
-
-
-
-
-
- {submission.pending ? "Submitting" : "Submit"}
-
-
- >
- )
-}
-```
+`useSubmission` returns a reactive object with the following properties:
-```tsx tab title="JavaScript" {6,10-12}
-// component.jsx
-import { Show } from "solid-js";
-import { useSubmission } from "@solidjs/router";
+### `input`
-function Component() {
- const submission = useSubmission(postNameAction);
-
- return (
- <>
-
- {(name) => Optimistic: {name()}
}
-
-
-
- {(name) => Result: {name()}
}
-
-
-
-
-
- {submission.pending ? "Submitting" : "Submit"}
-
-
- >
- )
-}
-```
+A reactive value representing the input data of the action.
+
+### `result`
-## Error Handling
+A reactive value representing the successful return value of the action.
-If the action fails, the `submission` object will be updated with the error and the `pending` property will be set to `false`.
-This allows you to provide feedback to the user that the action has failed. Additionally, the return type of `useSubmission` will have a new key `error` that will contain the error object thrown by the submission handler.
+### `error`
-At this stage, you can also use the `retry()` method to attempt the action again or the `clear()` to wipe the filled data in the platform.
+A reactive value representing any error thrown by the action.
-```tsx title="component.tsx" {12-18}
+### `pending`
+
+A reactive boolean indicating if the action is currently running.
+
+### `clear`
+
+A function to clear the submission's state.
+
+### `retry`
+
+A function to re-execute the submission with the same input.
+
+## Examples
+
+### Basic usage
+
+```tsx
import { Show } from "solid-js";
+import { action, useSubmission } from "@solidjs/router";
+
+const addTodoAction = action(async (formData: FormData) => {
+ const name = formData.get("name")?.toString();
+
+ if (!name || name.length <= 2) {
+ return { ok: false, message: "Name must be larger than 2 characters." };
+ }
+
+ // ... Sends the todo data to the server.
+
+ return { ok: true };
+}, "addTodo");
+
+function AddTodoForm() {
+ const submission = useSubmission(addTodoAction);
+
+ return (
+
+
+ {submission.pending ? "Adding..." : "Add"}
+
+
+
{submission.result.message}
+
submission.clear()}>Clear
+
submission.retry()}>Retry
+
+
+
+ );
+}
+```
+
+### Filtering submissions
+
+```tsx
import { useSubmission } from "@solidjs/router";
-function Component() {
- const submission = useSubmission(postNameAction);
-
- return (
- <>
-
- {(error) => (
-
-
Error: {error.message}
-
submission.clear()}>
- Clear
-
-
submission.retry()}>
- Retry
-
-
- )}
-
-
-
-
-
- {submission.pending ? "Submitting" : "Submit"}
-
-
- >
- )
+const addTodoAction = action(async (formData: FormData) => {
+ // ... Sends the todo data to the server.
+}, "addTodo");
+
+function LatestTodo() {
+ const latestValidSubmission = useSubmission(
+ addTodoAction,
+ ([formData]: [FormData]) => {
+ const name = formData.get("name")?.toString();
+ return name && name.length > 2;
+ }
+ );
+
+ return Latest valid submission: {latestValidSubmission.result}
;
}
```
diff --git a/src/routes/solid-router/reference/data-apis/use-submissions.mdx b/src/routes/solid-router/reference/data-apis/use-submissions.mdx
index c03ac618c..5a1899755 100644
--- a/src/routes/solid-router/reference/data-apis/use-submissions.mdx
+++ b/src/routes/solid-router/reference/data-apis/use-submissions.mdx
@@ -2,189 +2,146 @@
title: useSubmissions
---
-This helper is used to handle form submissions and can provide optimistic updates while actions are in flight as well as pending state feedback.
-This method will return an iterable of all submitted actions while its component is mounted. With an optional second parameter for a filter function.
+The `useSubmissions` primitive returns the state of all submissions for a given [action](/solid-router/concepts/actions).
-:::tip
-If you only care for the latest submission, you can use the [`useSubmission`](/solid-router/reference/data-apis/use-submission) helper.
-:::
+## Import
-It's important to note that it requires the form method to be **post** otherwise it will trigger a browser navigation and will not work.
-
-In the example below, the `useSubmissions` helper is used to retain a list of all submission results to that action while also giving feedback on the pending state of the current in-flight submission.
-
-```tsx title="component.tsx" {4,9-20, 23}
+```tsx
import { useSubmissions } from "@solidjs/router";
+```
-function Component() {
- const submissions = useSubmissions(postNameAction);
-
- return (
-
-
-
- {([attemptIndex, data]) => (
-
- { result => (
-
- Backend {attemptIndex}: {result.name}
-
- )}
-
- >
- )}
-
-
-
- {submissions.pending ? "sending" : "send"}
-
- )
-}
+## Type
+
+```tsx
+function useSubmissions, U, V>(
+ fn: Action,
+ filter?: (input: V) => boolean
+): Submission>[] & {
+ pending: boolean;
+};
```
-:::note
-To trigger a submission, [actions](https://docs.solidjs.com/) can be used.
-:::
+## Parameters
-## Filtering Submissions
+### `action`
-As an optional second parameter, the `useSubmissions` helper can receive a filter function to only return the submission that matches the condition.
-The filter receives the submitted dated as a parameter and should return a boolean value.
-E.g.: action below will only submit if the name is "solid".
+- **Type:** `Action`
+- **Required:** Yes
-```tsx title="component.tsx" {4-8}
-import { useSubmissions } from "@solidjs/router";
+The action to track.
-function Component() {
- const submissions = useSubmissions(postNameAction, ([formData]) => {
- const name = formData.get("name") ?? "";
-
- return name === "solid";
- });
-
- return (
-
-
-
- {([attemptIndex, data]) => (
-
- { result => (
-
- Backend {attemptIndex}: {result.name}
-
- )}
-
- >
- )}
-
-
-
- {submissions.pending ? "sending" : "send"}
-
- )
-}
-```
+### `filter`
-## Optimistic Updates
+- **Type:** `(input: V) => boolean`
+- **Required:** No
-When the form is submitted, the `submission` object will be updated with the new value and the `pending` property will be set to `true`.
-This allows you to provide feedback to the user that the action is in progress.
-Once the action is complete, the `pending` property will be set to `false` and the `result` property will be updated with final value.
+A function that filters submissions.
+It is executed for each submission in the order of creation.
+It receives an array of the action's inputs as a parameter and must return `true` to select the submission or `false` otherwise.
-```tsx tab title="TypeScript" {6,13-20}
-// component.tsx
-import { Show } from "solid-js";
-import { useSubmissions } from "@solidjs/router";
+## Return value
-function Component() {
- const submissions = useSubmissions(postNameAction);
-
- return (
-
-
-
- {([attemptIndex, data]) => (
-
- {(input) => {
- const name = (input().value as [string, string])[1]
-
- return (
- Optimistic: {name}
- )}}
-
- )}
-
-
-
- {submissions.pending ? "sending" : "send"}
-
- )
-}
-```
+`useSubmissions` returns a reactive array of submission objects.
+Each submission object has the following properties:
-```tsx tab title="JavaScript" {6,13-20}
-// component.jsx
-import { Show } from "solid-js";
-import { useSubmissions } from "@solidjs/router";
+### `input`
-function Component() {
- const submissions = useSubmissions(postNameAction);
-
- return (
-
-
-
- {([attemptIndex, data]) => (
-
- {(input) => {
- const name = input().value[1]
-
- return (
- Optimistic: {name}
- )}}
-
- )}
-
-
-
- {submissions.pending ? "sending" : "send"}
-
- )
-}
-```
+The reactive input data of the action.
+
+### `result`
-## Error Handling
+A reactive value representing the successful return value of the action.
-If the action fails, the `submission` object will be updated with the error and the `pending` property will be set to `false`.
-This allows you to provide feedback to the user that the action has failed. Additionally, the return type of `useSubmission` will have a new key `error` that will contain the error object thrown by the submission handler.
+### `error`
+
+A reactive value for any error thrown by the action.
+
+### `pending`
+
+A reactive boolean indicating if the action is currently running.
+
+### `clear`
+
+A function to clear the submission's state.
+
+### `retry`
+
+A function to re-execute the submission with the same input.
+
+## Examples
+
+### Basic usage
+
+```tsx
+import { For, Show } from "solid-js";
+import { action, useSubmissions } from "@solidjs/router";
+
+const addTodoAction = action(async (formData: FormData) => {
+ // ... Sends the todo data to the server.
+}, "addTodo");
+
+function AddTodoForm() {
+ const submissions = useSubmissions(addTodoAction);
+
+ return (
+
+
+
+ Add
+
+
+ {(submission) => (
+
+ Adding "{submission.input[0].get("name")?.toString()}"
+
+ (pending...)
+
+
+ (completed)
+
+
+ {` (Error: ${submission.result?.message})`}
+ submission.retry()}>Retry
+
+
+ )}
+
+
+ );
+}
+```
-At this stage, you can also use the `retry()` method to attempt the action again or the `clear()` to wipe the filled data in the platform.
+### Filtering submissions
-```tsx title="component.tsx" {12-18}
-import { Show } from "solid-js";
+```tsx
import { useSubmissions } from "@solidjs/router";
-function Component() {
- const submissions = useSubmissions(postNameAction);
-
- return (
-
-
-
- {([attempt, data]) => (
-
-
- Backend {attempt}: {data.error.message}
- data.retry()}>retry
- data.clear()}>clear
-
-
- )}
-
-
-
- {submissions.pending ? "sending" : "send"}
-
- )
+const addTodoAction = action(async (formData: FormData) => {
+ // ... Sends the todo data to the server.
+}, "addTodo");
+
+function FailedTodos() {
+ const failedSubmissions = useSubmissions(
+ addTodoAction,
+ ([formData]: [FormData]) => {
+ // Filters for submissions that failed a client-side validation
+ const name = formData.get("name")?.toString() ?? "";
+ return name.length <= 2;
+ }
+ );
+
+ return (
+
+
Failed submissions:
+
+ {(submission) => (
+
+ {submission.input[0].get("name")?.toString()}
+ submission.retry()}>Retry
+
+ )}
+
+
+ );
}
```
diff --git a/src/routes/solid-router/reference/preload-functions/preload.mdx b/src/routes/solid-router/reference/preload-functions/preload.mdx
index 71f27bc60..cf21d504b 100644
--- a/src/routes/solid-router/reference/preload-functions/preload.mdx
+++ b/src/routes/solid-router/reference/preload-functions/preload.mdx
@@ -1,46 +1,92 @@
---
-title: Preload
+title: preload
---
-With smart caches waterfalls are still possible with view logic and with lazy loaded code.
-With preload functions, fetching the data parallel to loading the route is possible to allow use of the data as soon as possible.
-The preload function can be called when the Route is loaded or eagerly when links are hovered.
+The `preload` function is a property on a route definition that initiates data fetching before a user navigates to the route.
-As its only argument, the preload function is passed an object that can be used to access route information:
+`preload` runs in two separate phases:
-```js
-import { lazy } from "solid-js";
+- **Preload phase:**
+ Triggered by user intent (e.g., hovering over a link), the function is called to initiate data fetching.
+- **Rendering phase:**
+ Triggered by actual navigation, the function is called a second time to provide the fetched data to the component.
+
+## Import
+
+```tsx
import { Route } from "@solidjs/router";
+```
-const User = lazy(() => import("./pages/users/[id].js"));
+## Type
-// preload function
-function preloadUser({ params, location }) {
- // do preloading
-}
+```tsx
+type RoutePreloadFunc = (args: RoutePreloadFuncArgs) => T;
-// Pass it in the route definition
- ;
+interface RoutePreloadFuncArgs {
+ params: Params;
+ location: Location;
+ intent: "initial" | "native" | "navigate" | "preload";
+}
```
-| key | type | description |
-| -------- | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| params | object | The route parameters (same value as calling [`useParams()`](/solid-router/reference/primitives/use-params) inside the route component) |
-| location | `{ pathname, search, hash, query, state, key}` | An object that used to get more information about the path (corresponds to [`useLocation()`](/solid-router/reference/primitives/use-location)) |
-| intent | `"initial", "navigate", "native", "preload"` | Indicates why this function is being called. "initial" - the route is being initially shown (ie page load) "native" - navigate originated from the browser (eg back/forward) "navigate" - navigate originated from the router (eg call to navigate or anchor clicked) "preload" - not navigating, just preloading (eg link hover) |
+## Parameters
-A common pattern is to export the preload function and data wrappers that correspond to a route in a dedicated `route.data.js` file.
-This imports the data functions without loading anything else.
+### `params`
-```js
-import { lazy } from "solid-js";
-import { Route } from "@solidjs/router";
-import preloadUser from "./pages/users/[id].data.js";
-const User = lazy(() => import("/pages/users/[id].js"));
+- **Type:** `Params`
-// In the Route definition
- ;
-```
+An object containing the parameters for the matched route.
+It corresponds to the value returned by the [`useParams` primitive](/solid-router/reference/primitives/use-params).
+
+### `location`
+
+- **Type:** `Location`
+
+The router's location object for the destination URL.
+It corresponds to the value returned by the [`useLocation` primitive](/solid-router/reference/primitives/use-location).
+
+### `intent`
+
+- **Type:** `"initial" | "native" | "navigate" | "preload"`
+
+A string indicating the context in which the function is called.
+
+- `"preload"`:
+ The function is running to initiate data fetching.
+- `"navigate"`:
+ The function is running during navigation to the route.
+- `"initial"`:
+ The function is running for the first route on page load.
-The return value of the `preload` function is passed to the page component when called at anytime other than `preload`.
-This initializes things in there, or alternatively the following new `Data APIs` can be used.
+## Return value
+
+The value returned by `preload` is passed to the route's component as the `data` prop.
+
+- In the **preload phase** (`intent: "preload"`), the return value is **ignored**.
+- In the **rendering phase** (`intent: "navigate"` or `"initial"`), the return value is **captured** and provided to the component.
+
+## Examples
+
+```tsx
+import { Route, query, createAsync } from "@solidjs/router";
+
+const getProductQuery = query(async (id: string) => {
+ // ... Fetches a product from the server.
+}, "product");
+
+function ProductPage(props) {
+ const product = createAsync(() => getProductQuery(props.params.id));
+
+ return {product()?.title}
;
+}
+
+function preloadData({ params }) {
+ getProductQuery(params.id);
+}
+
+function ProductRoutes() {
+ return (
+
+ );
+}
+```
diff --git a/src/routes/solid-start/building-your-application/data-fetching.mdx b/src/routes/solid-start/building-your-application/data-fetching.mdx
new file mode 100644
index 000000000..6204a6bb4
--- /dev/null
+++ b/src/routes/solid-start/building-your-application/data-fetching.mdx
@@ -0,0 +1,43 @@
+---
+title: "Data fetching"
+---
+
+Fetching data from a remote API or database is a core task for most applications.
+[Solid](/) and [Solid Router](/solid-router) provide foundational tools like the [`createResource` primitive](/guides/fetching-data) and [queries](/solid-router/concepts/queries) to manage asynchronous data.
+
+SolidStart builds on these capabilities, extending them to provide a comprehensive solution for data fetching in a full-stack environment.
+
+This page assumes you are familiar with the fundamental concepts of Solid and Solid Router.
+If you are a beginner, we highly recommend starting with the [queries documentation](/solid-router/concepts/queries).
+You can also find many practical examples in the [data fetching how-to guide](/solid-start/guides/data-fetching).
+
+## Server functions and queries
+
+Server functions provide a way to write functions that run exclusively on the server.
+This makes it safe to fetch data directly from a database without relying on a separate API endpoint.
+
+Server functions integrate seamlessly with queries, as they can be used as the fetcher for a query.
+
+```tsx
+import { query, redirect } from "@solidjs/router";
+import { useSession } from "vinxi/http";
+import { db } from "./db";
+
+const getCurrentUserQuery = query(async (id: string) => {
+ "use server";
+ const session = await useSession({
+ password: process.env.SESSION_SECRET as string,
+ name: "session",
+ });
+
+ if (session.data.userId) {
+ return await db.users.get({ id: session.data.userId });
+ } else {
+ throw redirect("/login");
+ }
+}, "currentUser");
+```
+
+In this example, the `getCurrentUserQuery` retrieves the session data, and if an authenticated user exists, it gets their information from the database and returns it.
+Otherwise, it redirects the user to the login page.
+All of these operations are performed completely on the server regardless of how the query is called.
diff --git a/src/routes/solid-start/building-your-application/data-loading.mdx b/src/routes/solid-start/building-your-application/data-loading.mdx
deleted file mode 100644
index 32445361e..000000000
--- a/src/routes/solid-start/building-your-application/data-loading.mdx
+++ /dev/null
@@ -1,157 +0,0 @@
----
-title: "Data loading"
----
-
-SolidStart aims to make it easy to load data from your data sources to keep your UI updated with your data.
-For most of your data requirements, routes will likely be used to decide what data to load.
-SolidStart includes nested routing to help structure your application's UI in a hierarchical way, so that you can share layouts.
-
-## Data loading on the client
-
-Solid provides a way to load data from your data sources using the [`createResource` primitive](/reference/basic-reactivity/create-resource).
-It takes an async function and returns a [signal](/reference/basic-reactivity/create-signal) from it.
-`createResource` integrates with [`Suspense`](/reference/components/suspense) and [`ErrorBoundary`](/reference/components/error-boundary) to help manage lifecycle and error states.
-
-```tsx tab title="TypeScript" {7-10}
-// src/routes/users.tsx
-import { For, createResource } from "solid-js";
-
-type User = { name: string; house: string };
-
-export default function Page() {
- const [users] = createResource(async () => {
- const response = await fetch("https://example.com/users");
- return (await response.json()) as User[];
- });
-
- return {(user) => {user.name} } ;
-}
-```
-
-```tsx tab title="JavaScript" {5-8}
-// src/routes/users.jsx
-import { For, createResource } from "solid-js";
-
-export default function Page() {
- const [users] = createResource(async () => {
- const response = await fetch("https://example.com/users");
- return (await response.json());
- });
-
- return {(user) => {user.name} } ;
-}
-```
-
-When fetching inside components, you can encounter unnecessary waterfalls, especially when nested under lazy loaded sections.
-To solve this, it is recommended to hoist the data fetching to the top of the component tree or, when in [SolidStart](/solid-start), use the server to fetch data in a non-blocking way.
-For the example below we will be using the data in APIs in [`solid-router`](/solid-router)
-
-Using some of the features of `solid-router`, we can create a cache for our data:
-
-```tsx tab title="TypeScript" {7, 10, 13}
-// /routes/users.tsx
-import { For } from "solid-js";
-import { createAsync, query } from "@solidjs/router";
-
-type User = { name: string; email: string };
-
-const getUsers = query(async () => {
- const response = await fetch("https://example.com/users");
- return (await response.json()) as User[];
-}, "users");
-
-export const route = {
- preload: () => getUsers(),
-};
-
-export default function Page() {
- const users = createAsync(() => getUsers());
-
- return {(user) => {user.name} } ;
-}
-```
-
-```tsx tab title="JavaScript" {5, 8, 11}
-// /routes/users.jsx
-import { For } from "solid-js";
-import { createAsync, query } from "@solidjs/router";
-
-const getUsers = query(async () => {
- const response = await fetch("https://example.com/users");
- return (await response.json());
-}, "users");
-
-export const route = {
- preload: () => getUsers(),
-};
-
-export default function Page() {
- const users = createAsync(() => getUsers());
-
- return {(user) => {user.name} } ;
-}
-```
-
-With this method, however, there are some caveats to be aware of:
-
-1. The [`preload`](/solid-router/reference/preload-functions/preload) function is called **once** per route, which is the first time the user comes to that route.
- Following that, the fine-grained resources that remain alive synchronize with state/url changes to refetch data when needed.
- If the data needs a refresh, the [`refetch`](/guides/fetching-data#refetch) function returned in the `createResource` can be used.
-2. Before the route is rendered, the `preload` function is called.
- It does not share the same `context` as the route.
- The context tree that is exposed to the `preload` function is anything above the `Page` component.
-3. On both the server and the client, the `preload` function is called.
- The resources can avoid refetching if they serialized their data in the server render.
- The server-side render will only wait for the resources to fetch and serialize if the resource signals are accessed under a `Suspense` boundary.
-
-### Data loading always on the server
-
-An advantage of being a full-stack JavaScript framework is that it is easy to write data loading code that can run both on the server and client.
-SolidStart offers a way to do that and more.
-Through the `"use server"` comment you can tell the bundler to create an RPC and not include the code in the clients bundle.
-This lets you write code that only runs on the server without needing to create an API route for it.
-For example, it could be database access or internal APIs, or when you sit within your function and need to use your server.
-
-```tsx tab title="TypeScript" {8}
-// /routes/users.tsx
-import { For } from "solid-js";
-import { createAsync, query } from "@solidjs/router";
-
-type User = { name: string; email: string };
-
-const getUsers = query(async () => {
- "use server";
- return store.users.list();
-}, "users");
-
-export const route = {
- preload: () => getUsers(),
-};
-
-export default function Page() {
- const users = createAsync(() => getUsers());
-
- return {(user) => {user.name} } ;
-}
-```
-
-```tsx tab title="JavaScript" {6}
-// /routes/users.jsx
-import { For } from "solid-js";
-import { createAsync, query } from "@solidjs/router";
-
-const getUsers = query(async () => {
- "use server";
- return store.users.list();
-}, "users");
-
-export const route = {
- preload: () => getUsers(),
-};
-
-export default function Page() {
- const users = createAsync(() => getUsers());
-
- return {(user) => {user.name} } ;
-}
-```
diff --git a/src/routes/solid-start/building-your-application/data-mutation.mdx b/src/routes/solid-start/building-your-application/data-mutation.mdx
new file mode 100644
index 000000000..2692c4dfc
--- /dev/null
+++ b/src/routes/solid-start/building-your-application/data-mutation.mdx
@@ -0,0 +1,104 @@
+---
+title: "Data mutation"
+---
+
+Mutating data on a server is a common task in most applications.
+[Solid Router](/solid-router) provides [actions](/solid-router/concepts/actions) to manage data mutations effectively.
+
+SolidStart builds upon the capabilities of actions, extending their scope to provide a comprehensive, full-stack solution for data mutations.
+
+This page does not cover the foundational concepts from Solid Router.
+If you are a beginner, we highly recommend starting with the [actions documentation](/solid-router/concepts/actions).
+You can also find many practical examples in the [data mutation how-to guide](/solid-start/guides/data-mutation).
+
+## Server functions and actions
+
+Server functions allow an action to run exclusively on the server.
+This enables performing sensitive operations—such as writing to a database or working with sessions—directly within the action.
+
+```tsx
+import { action, redirect } from "@solidjs/router";
+import { useSession } from "vinxi/http";
+import { db } from "./db";
+
+const logoutAction = action(async () => {
+ "use server";
+ const session = await useSession({
+ password: process.env.SESSION_SECRET as string,
+ name: "session",
+ });
+
+ if (session.data.sessionId) {
+ await session.clear();
+ await db.session.delete({ id: sessionId });
+ }
+
+ throw redirect("/");
+}, "logout");
+```
+
+In this example, the entire `logoutAction` runs on the server.
+It safely accesses the session to retrieve the `sessionId` and performs a database deletion without exposing this logic to the client.
+The `redirect` then navigates the user back to the home page.
+
+## Single-flight mutations
+
+When a piece of data changes on the server, the new data needs to be fetched so the UI doesn't fall out of sync.
+Traditionally, this is done in two separate HTTP requests: one to update the data, and a second to fetch the new data.
+
+Single-flight mutations are a unique feature of SolidStart that handles this pattern in a single request.
+This is enabled when two requirements are met:
+
+1. The action that updates the data must execute on the server using server functions.
+2. The data that the action updated must be preloaded.
+ If the action performs a redirect, preloading needs to happen on the destination page.
+
+```tsx title="src/routes/products/[id].tsx"
+import {
+ action,
+ query,
+ createAsync,
+ type RouteDefinition,
+ type RouteSectionProps,
+} from "@solidjs/router";
+import { db } from "./db";
+
+const updateProductAction = action(async (id: string, formData: FormData) => {
+ "use server";
+ const name = formData.get("name")?.toString();
+ await db.products.update(id, { name });
+}, "updateProduct");
+
+const getProductQuery = query(async (id: string) => {
+ "use server";
+ return await db.products.get(id);
+}, "product");
+
+export const route = {
+ preload: ({ params }) => getProductQuery(params.id as string),
+} satisfies RouteDefinition;
+
+export default function ProductDetail(props: RouteSectionProps) {
+ const product = createAsync(() => getProductQuery(props.params.id as string));
+
+ return (
+
+
Current name: {props.data.product?.name}
+
+
+ Save
+
+
+ );
+}
+```
+
+In this example, `updateProductAction` updates the product within a server function, and `getProductQuery` is responsible for fetching the product data.
+Note that `getProductQuery` is preloaded on the route.
+
+When a user submits the form, a single POST request is sent to the server.
+After the action completes, `getProductQuery` is automatically revalidated.
+Because it's preloaded, SolidStart can trigger the revalidation on the server and stream the result back to the client in the same response.
diff --git a/src/routes/solid-start/building-your-application/data.json b/src/routes/solid-start/building-your-application/data.json
index 2da1baa8d..7d5641d01 100644
--- a/src/routes/solid-start/building-your-application/data.json
+++ b/src/routes/solid-start/building-your-application/data.json
@@ -4,7 +4,8 @@
"routing.mdx",
"api-routes.mdx",
"css-and-styling.mdx",
- "data-loading.mdx",
+ "data-fetching.mdx",
+ "data-mutation.mdx",
"head-and-metadata.mdx",
"route-prerendering.mdx",
"static-assets.mdx"
diff --git a/src/routes/solid-start/guides/data-fetching.mdx b/src/routes/solid-start/guides/data-fetching.mdx
index 5a1a4c38a..6707b6d95 100644
--- a/src/routes/solid-start/guides/data-fetching.mdx
+++ b/src/routes/solid-start/guides/data-fetching.mdx
@@ -2,17 +2,9 @@
title: "Data fetching"
---
-SolidStart is built on top of [Solid](/) and uses [Solid Router](/solid-router) by default.
-This means you can leverage their respective data-fetching primitives within SolidStart.
-Since SolidStart itself provides minimal data-fetching APIs, most functionality comes from Solid and Solid Router.
+This guide provides practical examples of common data-fetching tasks in SolidStart.
-This guide provides practical examples of common data-fetching tasks using these primitives.
-
-:::note
-For detailed API information, refer to the [Solid](/) and [Solid Router](/solid-router) documentation.
-:::
-
-Here's a simple example:
+Here's an example showing how to create a [`query`](/solid-router/concepts/queries) and access its data with the [`createAsync` primitive](/solid-router/reference/data-apis/create-async):
```tsx tab title="TypeScript"
// src/routes/index.tsx
@@ -54,12 +46,9 @@ export default function Page() {
}
```
-In this example, a [`query`](/solid-router/reference/data-apis/query) is created.
-In order to access it's data within the component, the [`createAsync`](/solid-router/reference/data-apis/create-async) primitive was used.
-
## Showing loading UI
-To show a loading UI during data-fetching:
+To show a loading UI during data fetching:
1. Import [`Suspense`](/reference/components/suspense) from `solid-js`.
2. Wrap your data rendering in ``, and use the `fallback` prop to show a component during data fetching.
@@ -110,7 +99,7 @@ export default function Page() {
## Handling errors
-To show a fallback UI if the data-fetching fails:
+To show a fallback UI if the data fetching fails:
1. Import [`ErrorBoundary`](/reference/components/error-boundary) from `solid-js`.
2. Wrap the data rendering in ``, and use the `fallback` prop to show a component if an error occurs.
@@ -165,7 +154,7 @@ export default function Page() {
## Preloading data
-Data fetching can be optimized during user navigation by preloading the data:
+To preload data before a route renders:
1. Export a `route` object with a [`preload`](/solid-router/reference/preload-functions/preload) function.
2. Run your query inside the `preload` function.
@@ -225,7 +214,7 @@ export default function Page() {
## Passing parameters to queries
-When creating a query that accepts parameters, define your query function to take any number of arguments:
+When creating a query that accepts parameters, define your query function to take any number of parameters:
```tsx tab title="TypeScript" {5} {11} {16}
// src/routes/posts/[id]/index.tsx
@@ -283,7 +272,7 @@ export default function Page() {
## Using a database or an ORM
-To safely interact with your database or ORM in a query, ensure it's server-only by adding [`"use server"`](/solid-start/reference/server/use-server) as the first line of your query:
+To safely interact with your database or ORM in a query, use a [server function](/solid-start/reference/server/use-server):
```tsx tab title="TypeScript" {7-8}
// src/routes/index.tsx
@@ -388,5 +377,5 @@ export default function Page() {
See the [`createResource`](/reference/basic-reactivity/create-resource) API reference for more information.
:::note[Advanced Data Handling]
-For advanced features like automatic background re-fetching or infinite queries, you can use [Tanstack Query](https://tanstack.com/query/latest/docs/framework/solid/overview).
+For advanced features like automatic background re-fetching or infinite queries, you can use [TanStack Query](https://tanstack.com/query/latest/docs/framework/solid/overview).
:::
diff --git a/src/routes/solid-start/guides/data-mutation.mdx b/src/routes/solid-start/guides/data-mutation.mdx
index abcc75f27..e2bcced15 100644
--- a/src/routes/solid-start/guides/data-mutation.mdx
+++ b/src/routes/solid-start/guides/data-mutation.mdx
@@ -12,7 +12,7 @@ To handle [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/f
See the [Action API reference](/solid-router/reference/data-apis/action#notes-of-form-implementation-and-ssr) for more information.
2. Pass the action to the ` ` element using the `action` prop.
3. Ensure the ` ` element uses the `post` method for submission.
-4. Use the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) object in the action to extract field data using the navite `FormData` methods.
+4. Use the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) object in the action to extract field data using the native `FormData` methods.
```tsx tab title="TypeScript" {4-10} {14}
// src/routes/index.tsx
@@ -486,12 +486,12 @@ export default function Page() {
}
```
-## Invoking an action programmatically
+## Triggering an action programmatically
-To programmatically invoke an action:
+To programmatically trigger an action:
1. Import [`useAction`](/solid-router/reference/data-apis/use-action) from `@solidjs/router`.
-2. Call `useAction` with your action, and use the returned function to invoke the action.
+2. Call `useAction` with your action, and use the returned function to trigger the action.
```tsx tab title="TypeScript" {14} {18}
// src/routes/index.tsx
diff --git a/src/routes/solid-start/reference/server/use-server.mdx b/src/routes/solid-start/reference/server/use-server.mdx
index 87dbad035..6c4cc6d33 100644
--- a/src/routes/solid-start/reference/server/use-server.mdx
+++ b/src/routes/solid-start/reference/server/use-server.mdx
@@ -44,7 +44,7 @@ In both examples, the `logHello` function will only show in the server console,
## Usage with Data APIs
-Server functions can be used for fetching data and performing actions on the server.
+Server functions can be used for fetching data and performing actions on the server.
The following examples show how to use server functions alongside solid-router's data APIs.
```tsx {3}
@@ -61,12 +61,12 @@ const updateUser = action(async (id, data) => {
```
-When `getUser` or `updateUser` are invoked on the client, an http request will be made to the server, which calls the corresponding server function.
+When `getUser` or `updateUser` are triggered on the client, an http request will be made to the server, which calls the corresponding server function.
## Single-flight mutations
-In the above example, when the `updateUser` action is called, a redirect is thrown on the server.
-Solid Start can handle this redirect on the server instead of propagating it to the client.
+In the above example, when the `updateUser` action is called, a redirect is thrown on the server.
+Solid Start can handle this redirect on the server instead of propagating it to the client.
The data for the redirected page is fetched and streamed to the client in the same http request as the `updateUser` action, rather than the client requiring a separate http request for the redirected page.
## Serialization