From 39f3b9f8492aff90e2ae7d4dc0c5195e1b92a5ad Mon Sep 17 00:00:00 2001 From: "Amir H. Hashemi" <87268103+amirhhashemi@users.noreply.github.com> Date: Fri, 16 May 2025 11:46:09 +0330 Subject: [PATCH 1/6] Fix useSubmission --- .../reference/data-apis/use-submission.mdx | 202 ++++++------------ 1 file changed, 61 insertions(+), 141 deletions(-) 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 7153e664cd..b4e9f969d0 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,80 @@ 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` function returns a submission object for a specified action. +This submission object contains properties to access the state of action execution and functions to control the action. -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. - -```tsx title="component.tsx" {4,8} -import { useSubmission } from "@solidjs/router"; - -function Component() { - const submission = useSubmission(postNameAction); - - return ( -
- - -
- ) +```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.length <= 2) { + throw new Error("Name must be larger than 2 characters"); + } +}, "addTodo"); + +function AddTodoForm() { + const submission = useSubmission(addTodoAction); + return ( +
+ + + + {(error) => ( +
+

{error().message}

+ + +
+ )} +
+
+ ); } ``` -:::note -Learn more about actions in the [`action`](/solid-router/reference/data-apis/action) docs. +:::info[Note] +If an action is executed multiple times, the last submission will be returned. +To access all submissions, `useSubmissions`[/solid-router/reference/data-apis/use-submissions] can be used. ::: -## Filtering Submissions - -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". - -```tsx title="component.tsx" {4-8} -import { useSubmission } from "@solidjs/router"; - -function Component() { - const submission = useSubmission(postNameAction, ([formData]) => { - const name = formData.get("name") ?? ""; - - return name === "solid"; - }); - - return ( -
- - -
- ) -} -``` - -## Optimistic Updates - -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. - -```tsx tab title="TypeScript" {6,10-12} -// component.tsx -import { Show } from "solid-js"; -import { useSubmission } from "@solidjs/router"; - -function Component() { - const submission = useSubmission(postNameAction); - - return ( - <> - - {(name) =>
Optimistic: {name() as string}
} -
+## Filter function - - {(name) =>
Result: {name()}
} -
+Optionally, `useSubmission` accepts a second parameter, which is a filter function. +This function is executed for each submission and returns the first submission that passes through the filter. +The filter function takes the submitted data as its parameter and should return `true` to select the submission and `false` otherwise. -
- - -
- - ) -} -``` - -```tsx tab title="JavaScript" {6,10-12} -// component.jsx -import { Show } from "solid-js"; +```tsx import { useSubmission } from "@solidjs/router"; - -function Component() { - const submission = useSubmission(postNameAction); - - return ( - <> - - {(name) =>
Optimistic: {name()}
} -
- - - {(name) =>
Result: {name()}
} -
- -
- - -
- - ) +import { addTodoAction } from "./actions"; + +function LatestTodo() { + const latestValidSubmission = useSubmission( + addTodoAction, + ([formData]: [FormData]) => { + const name = formData.get("name")?.toString() ?? ""; + return name.length > 2; + } + ); + return

Latest valid submittion: {latestValidSubmission.result}

; } ``` -## Error Handling - -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. - -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. +## Parameters -```tsx title="component.tsx" {12-18} -import { Show } from "solid-js"; -import { useSubmission } from "@solidjs/router"; +- **action**: The action for which you want to return submissions. +- **filter** (Optional): The filter function that receives the submitted data as its parameter. + It should return `true` if the submission passes the filter and `false` otherwise. -function Component() { - const submission = useSubmission(postNameAction); +## Returns - return ( - <> - - {(error) => ( -
-

Error: {error.message}

- - -
- )} -
+`useSubmission` returns an object containing the following properties: -
- - -
- - ) -} -``` +- **input**: The input data of the action. +- **result**: The returned value of the action. +- **error**: Any error thrown from the action. +- **pending**: A boolean indicating whether the action is currently being executed. +- **clear**: A function to clear the results of the submission. +- **retry**: A function to re-execute the action. From f3c6bd051aafbd65fb5805b7cddb4c6a7ea6a783 Mon Sep 17 00:00:00 2001 From: "Amir H. Hashemi" <87268103+amirhhashemi@users.noreply.github.com> Date: Sun, 8 Jun 2025 11:42:11 +0330 Subject: [PATCH 2/6] Update --- .../reference/data-apis/use-submission.mdx | 31 +- .../reference/data-apis/use-submissions.mdx | 264 ++++++------------ 2 files changed, 110 insertions(+), 185 deletions(-) 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 b4e9f969d0..962efa5349 100644 --- a/src/routes/solid-router/reference/data-apis/use-submission.mdx +++ b/src/routes/solid-router/reference/data-apis/use-submission.mdx @@ -2,8 +2,7 @@ title: useSubmission --- -The `useSubmission` function returns a submission object for a specified action. -This submission object contains properties to access the state of action execution and functions to control the action. +The `useSubmission` function retrieves the state of the most recent submission for a given action. ```tsx import { Show } from "solid-js"; @@ -37,15 +36,14 @@ function AddTodoForm() { ``` :::info[Note] -If an action is executed multiple times, the last submission will be returned. -To access all submissions, `useSubmissions`[/solid-router/reference/data-apis/use-submissions] can be used. +To access the state of all submissions, `useSubmissions`[/solid-router/reference/data-apis/use-submissions] can be used. ::: ## Filter function -Optionally, `useSubmission` accepts a second parameter, which is a filter function. -This function is executed for each submission and returns the first submission that passes through the filter. -The filter function takes the submitted data as its parameter and should return `true` to select the submission and `false` otherwise. +The `useSubmission` function optionally accepts a second parameter, which is a filter function. +This function is executed for each submission in the order they were created, and the first submission that meet the filter criteria is returned by `useSubmission`. +The filter function recieves the input data of the action as its parameter and must return `true` to select the submission or `false` otherwise. ```tsx import { useSubmission } from "@solidjs/router"; @@ -59,23 +57,28 @@ function LatestTodo() { return name.length > 2; } ); - return

Latest valid submittion: {latestValidSubmission.result}

; + return

Latest valid submission: {latestValidSubmission.result}

; } ``` ## Parameters -- **action**: The action for which you want to return submissions. -- **filter** (Optional): The filter function that receives the submitted data as its parameter. - It should return `true` if the submission passes the filter and `false` otherwise. +- **action**: The action for which you want to get the most recent submission. +- **filter** (optional): A filter function. + When provided, it executes on each submission in the order of creation, returning the first submission that passes the filter. + It receives the input data of the action as its parameter and must return `true` for the submission to be selected and `false` otherwise. ## Returns `useSubmission` returns an object containing the following properties: - **input**: The input data of the action. -- **result**: The returned value of the action. + This is a reactive value. +- **result**: The value returned from the action. + This is a reactive value. - **error**: Any error thrown from the action. + This is a reactive value. - **pending**: A boolean indicating whether the action is currently being executed. -- **clear**: A function to clear the results of the submission. -- **retry**: A function to re-execute the action. + This is a reactive value. +- **clear**: A function that clears the result of the submission. +- **retry**: A function that re-executes the submission with the same input. 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 c03ac618ca..e5f10d17ce 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,111 @@ 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. - -:::tip -If you only care for the latest submission, you can use the [`useSubmission`](/solid-router/reference/data-apis/use-submission) helper. -::: - -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} -import { useSubmissions } from "@solidjs/router"; - -function Component() { - const submissions = useSubmissions(postNameAction); - - return ( -
- - - -
- ) +The `useSubmissions` function retrieves the state of all submissions for a given action. + +```tsx +import { For, Show } from "solid-js"; +import { action, useSubmissions } from "@solidjs/router"; + +const addTodoAction = action(async (formData: FormData) => { + const name = formData.get("name")?.toString() ?? ""; + if (name.length <= 2) { + throw new Error("Name must be larger than 2 characters"); + } + return name; +}, "addTodo"); + +export default function AddTodoForm() { + const submissions = useSubmissions(addTodoAction); + return ( +
+
+ + +
+ + {(submission) => ( +
+ Adding "{submission.input[0].get("name")?.toString()}" + + (pending...) + + + (completed) + + + {(error) => ( + <> + {` (Error: ${error().message})`} + + + )} + +
+ )} +
+
+ ); } ``` -:::note -To trigger a submission, [actions](https://docs.solidjs.com/) can be used. +:::info[Note] +To access the state of the most recent submission, `useSubmission`[/solid-router/reference/data-apis/use-submission] can be used. ::: -## Filtering Submissions +## Filter function -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". +The `useSubmissions` function optionally accepts a second parameter, which is a filter function. +This function is executed for each submission in the order they were created, and only the submissions that meet the filter criteria are returned by `useSubmissions`. +The filter function recieves the input data of the action as its parameter and must return `true` to select the submission or `false` otherwise. -```tsx title="component.tsx" {4-8} +```tsx import { useSubmissions } from "@solidjs/router"; - -function Component() { - const submissions = useSubmissions(postNameAction, ([formData]) => { - const name = formData.get("name") ?? ""; - - return name === "solid"; - }); - - return ( -
- - - -
- ) +import { addTodoAction } from "./actions"; + +function FailedTodos() { + const failedSubmissions = useSubmissions( + addTodoAction, + ([formData]: [FormData]) => { + const name = formData.get("name")?.toString() ?? ""; + return name.length <= 2; + } + ); + return ( +
+

Failed submissions:

+ + {(submission) => ( +
+ {submission.input[0].get("name")?.toString()} + +
+ )} +
+
+ ); } ``` -## Optimistic Updates - -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. +## Parameters -```tsx tab title="TypeScript" {6,13-20} -// component.tsx -import { Show } from "solid-js"; -import { useSubmissions } from "@solidjs/router"; - -function Component() { - const submissions = useSubmissions(postNameAction); +- **action**: The action for which you want to get the submissions. +- **filter** (optional): A filter function. + When provided, it executes on each submission in the order of creation, returning the submissions that pass the filter. + It receives the input data of the action as its parameter and must return `true` to select the submission and `false` otherwise. - return ( -
- - - -
- ) -} -``` +`useSubmissions` returns an array of submissions. +Each submission is an object containing the following properties: -```tsx tab title="JavaScript" {6,13-20} -// component.jsx -import { Show } from "solid-js"; -import { useSubmissions } from "@solidjs/router"; - -function Component() { - const submissions = useSubmissions(postNameAction); - - return ( -
- - - -
- ) -} -``` - -## Error Handling - -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. - -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. - -```tsx title="component.tsx" {12-18} -import { Show } from "solid-js"; -import { useSubmissions } from "@solidjs/router"; - -function Component() { - const submissions = useSubmissions(postNameAction); - - return ( -
- - - -
- ) -} -``` +- **input**: The input data of the action. + This is a reactive value. +- **result**: The value returned from the action. + This is a reactive value. +- **error**: Any error thrown from the action. + This is a reactive value. +- **pending**: A boolean indicating whether the action is currently being executed. + This is a reactive value. +- **clear**: A function that clears the result of the submission. +- **retry**: A function that re-executes the submission with the same input. From e9d0891209e924de5f9beaa147c352587366075e Mon Sep 17 00:00:00 2001 From: "Amir H. Hashemi" <87268103+amirhhashemi@users.noreply.github.com> Date: Wed, 11 Jun 2025 18:58:40 +0330 Subject: [PATCH 3/6] Update --- .../reference/data-apis/action.mdx | 272 +++++++----------- 1 file changed, 104 insertions(+), 168 deletions(-) diff --git a/src/routes/solid-router/reference/data-apis/action.mdx b/src/routes/solid-router/reference/data-apis/action.mdx index 8f3a5e666a..5c9f48e580 100644 --- a/src/routes/solid-router/reference/data-apis/action.mdx +++ b/src/routes/solid-router/reference/data-apis/action.mdx @@ -2,196 +2,132 @@ title: action --- -Actions are data mutations that can trigger invalidations and further routing. -A list of prebuilt response helpers can be found below. - -```jsx -import { action, revalidate, redirect } from "@solidjs/router" - -// anywhere -const myAction = action(async (data) => { - await doMutation(data); - throw redirect("/", { revalidate: getUser.keyFor(data.id) }); // throw a response to do a redirect +The `action` function wraps an asynchronous function and returns an action. +Actions enable data mutations and side effects, often in response to user interactions such as form submissions. +To learn more about actions, see [the actions documentation](/solid-router/concepts/actions). + +```tsx +import { action } from "@solidjs/router"; + +const addTodoAction = action(async (name: string) => { + await fetch("https://api.com/todos", { + method: "POST", + body: JSON.stringify({ name }), + }); }); - -// in component -
- -//or - - ``` -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. +## Forms -Without `with`: +Actions can be used to handle form submissions by passing them to the `action` prop in the [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form) element. +The form values will be accessible through the first parameter of the action function, which is a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object. -```jsx -const deleteTodo = action(async (formData: FormData) => { - const id = Number(formData.get("id")) - await api.deleteTodo(id) -}) +Please note that the `` element **must** have `method="post"`. +When using Server-Side Rendering (SSR), you must provide a unique name as the second parameter to the action function. - - - -
+```tsx +import { action } from "@solidjs/router"; -``` - -Using `with`: - -```jsx del={5,6} ins={7} -const deleteTodo = action(async (id: number) => { - await api.deleteTodo(id) -}) - -
- - - -
- -``` - -:::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. -::: - -## Notes of `
` implementation and SSR - -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. - -```jsx -const myAction = action(async (args) => {}, "my-action"); -``` - -## `useAction` - -Instead of forms, actions can directly be wrapped in a `useAction` primitive. -This is how router context is created. - -```jsx -// in component -const submit = useAction(myAction); -submit(...args); +const addTodoAction = action(async (formData: FormData) => { + const name = formData.get("name")?.toString(); + await fetch("https://api.com/todos", { + method: "POST", + body: JSON.stringify({ name }), + }); +}, "add-todo"); +function TodoForm() { + return ( + + + +
+ ); +} ``` -The outside of a form context can use custom data instead of `formData`. -These helpers preserve types. +### The `with` method -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. +Actions have a `with` method that is used to pass additional arguments to the action. +The parameters passed to the `with` method are forwarded to the action function in the same order. +The `FormData` is still available as the last parameter. -## `useSubmission`/`useSubmissions` +```tsx +import { action } from "@solidjs/router"; -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. +const addTodoAction = action(async (userId: number, formData: FormData) => { + const name = formData.get("name")?.toString(); + await fetch("https://api.com/todos", { + method: "POST", + body: JSON.stringify({ userId, name }), + }); +}); -```jsx -type Submission = { - input: T; - result: U; - error: any; - pending: boolean - clear: () => {} - retry: () => {} +function TodoForm() { + const userId = 1; + return ( +
+ + +
+ ); } - -const submissions = useSubmissions(action, (input) => filter(input)); -const submission = useSubmission(action, (input) => filter(input)); - -``` - -## Revalidate cached functions - -### Revalidate all (default) -By default all cached functions will be revalidated wether the action does not return or return a "normal" response. - -```jsx - -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)]}) - -}) - +## Response helpers + +Solid Router provides three response helpers that customize the behavior of the action: + +- [`json`](/solid-router/reference/response-helpers/json): Allows returning JSON from the action, which will be accessible as the return value when invoking the action using [`useAction`](/solid-router/reference/data-apis/use-action). +- [`redirect`](/solid-router/reference/response-helpers/redirect): Performs a redirect. +- [`reload`](/solid-router/reference/response-helpers/reload): Revalidates queries. + +Response helpers return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) object with custom properties recognized by Solid Router. +When a `Response` object is either returned or thrown from an action function, Solid Router handles it automatically. +It's advised not to construct a `Response` object without using response helpers. + +```tsx +import { action, redirect, json } from "@solidjs/router"; +import { getCurrentUser } from "./auth"; + +const addTodoAction = action(async (name: string) => { + const user = await getCurrentUser(); + if (!user) { + throw redirect("/login"); + // Or + // return redirect("/login"); + } + + const todo = await fetch("https://api.com/todos", { + method: "POST", + body: JSON.stringify({ name }), + }).then((response) => response.json()); + throw json({ todo }); + // Or + // return json({ 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" }) +## Query revalidation - // or reload the route without revalidating the request. - return reload({ revalidate: "nothing" }) - - // or redirect without revalidating - return redirect("/", { revalidate: "nothing" }) -}) - -``` +By default, when an action is invoked, all [queries](/solid-router/reference/data-apis/query) used on the same page are automatically revalidated. +This behavior can be customized using the [response helpers](#response-helpers). -### Revalidate without action +Response helpers accept a parameter that allows to specify which query keys to revalidate in an action. +This can be used to disable revalidation for a specific set of queries or to disable it entirely. -Cached functions can also be revalidated by the `revalidate` helper. - -```jsx -revalidate([getTodos.key, getTodoByID.keyFor(id)]) +```tsx +import { action, reload } from "@solidjs/router"; +const addTodoAction = action(async (name: string) => { + await fetch("https://api.com/todos", { + method: "POST", + body: JSON.stringify({ name }), + }); + return reload({ revalidate: [] }); + // return reload({ revalidate: ["getTodos"] }); +}); ``` -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)) - }) - - // ... -} - -``` +In this example, the first `reload` completely disables revalidation. +The second return (which is commented out) only revalidates queries with the `getTodos` query key. From 5a7ec5c489031915c4455d88f99600769446119c Mon Sep 17 00:00:00 2001 From: Amir Hossein Hashemi <87268103+amirhhashemi@users.noreply.github.com> Date: Thu, 19 Jun 2025 01:20:19 +0330 Subject: [PATCH 4/6] Update info callout titles (#1207) --- .../solid-router/reference/data-apis/use-submission.mdx | 2 +- src/ui/callout.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) 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 962efa5349..4c2055bcbc 100644 --- a/src/routes/solid-router/reference/data-apis/use-submission.mdx +++ b/src/routes/solid-router/reference/data-apis/use-submission.mdx @@ -35,7 +35,7 @@ function AddTodoForm() { } ``` -:::info[Note] +:::note To access the state of all submissions, `useSubmissions`[/solid-router/reference/data-apis/use-submissions] can be used. ::: diff --git a/src/ui/callout.tsx b/src/ui/callout.tsx index 9878c4266a..a69862a883 100644 --- a/src/ui/callout.tsx +++ b/src/ui/callout.tsx @@ -101,7 +101,11 @@ export function Callout(props: CalloutProps) { +======= + +>>>>>>> 0c3b40b1 (Update info callout titles (#1207)) {props.type || "Note"} } From aec869e9c7b82496f08c274032ce3fe8c99085ff Mon Sep 17 00:00:00 2001 From: "Amir H. Hashemi" <87268103+amirhhashemi@users.noreply.github.com> Date: Wed, 2 Jul 2025 07:38:29 +0330 Subject: [PATCH 5/6] update --- src/routes/solid-router/concepts/actions.mdx | 231 ++++++++++-------- .../reference/data-apis/use-submission.mdx | 2 +- .../reference/data-apis/use-submissions.mdx | 4 +- 3 files changed, 126 insertions(+), 111 deletions(-) diff --git a/src/routes/solid-router/concepts/actions.mdx b/src/routes/solid-router/concepts/actions.mdx index 53a96da9c5..0f8af92cef 100644 --- a/src/routes/solid-router/concepts/actions.mdx +++ b/src/routes/solid-router/concepts/actions.mdx @@ -2,151 +2,166 @@ 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. +Actions provide a powerful and flexible mechanism for handling data mutations and side effects. +They are designed to simplify your application's data flow, ensure a consistent user experience, and integrate seamlessly with Solid's reactivity. -## What are actions? +Actions provide several key 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. - -### How actions work - -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. - -When a user performs an action, such as submitting a form, the data is sent to the server for processing via an action. - -### Benefits of using actions - -1. **Isomorphic**: Since actions can run on both the server and client, you can optimize performance by choosing the best execution environment for your needs. -2. **Asynchronous processing**: Actions handle data submissions asynchronously, ensuring that your application remains responsive. -3. **Simplified data handling**: By using actions, the process of managing and tracking data submissions can be streamlined, reducing the complexity of your application. +- **Centralized Logic**: Encapsulate the logic for data modifications in a single, reusable function. +- **Integrated State Management**: Solid Router automatically tracks the execution state of an action (whether it's pending, successful, or has encountered an error), making it easy to build reactive UI feedback. +- **Automatic Data Revalidation**: By default, after an action successfully completes, Solid Router revalidates any queries on the same page. + This ensures your UI reflects the latest data without manual intervention. +- **Progressive Enhancement**: When used with HTML forms, actions can enable forms to function even if JavaScript is not yet loaded, providing a robust and accessible user experience. ## Creating actions -To create an action, use the `action` function from the `@solidjs/router` package. -This function takes an asynchronous function as an argument and returns a new function that can be used to submit data. +At their core, actions are **asynchronous functions** that you define using the `action` function. +The [`action`](/solid-router/reference/data-apis/action) function takes your asynchronous function and returns an action object. + +To define an action, import the `action` function from `@solidjs/router` and pass it your asynchronous logic: ```tsx import { action } from "@solidjs/router"; -const echo = action(async (message: string) => { - // Simulates an asynchronous operation, such as an API call - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); - console.log(message); +const addPostAction = action(async (title: string) => { + const response = await fetch("https://api.com/posts", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ title }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || "Failed to add post"); + } + + return await response.json(); }); ``` -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, `addPostAction` handles sending a POST request to create a new post. +The return value of the action can be accessed later when tracking action state. -:::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. +:::note[Server-Side Rendering (SSR)] +When using actions with SSR, you must provide a unique name string as the second parameter to the `action` function. +This is crucial for Solid Router to correctly identify and re-run actions on the server. +We'll explore this in more detail in the Handling Form Submissions section. ::: -### Using actions - -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. - -```tsx del={1} ins={2,9-13} -import { action } from "@solidjs/router"; -import { action, useAction } from "@solidjs/router"; - -const echo = action(async (message: string) => { - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); - console.log(message); -}); - -export function MyComponent() { - const myEcho = useAction(echo); - - myEcho("Hello from Solid!"); -} -``` - -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. - -### Returning data from actions +## How to Use Actions -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. +Solid Router offers two primary ways to invoke an action: -To access the action's result, you must pass the action to `useSubmission`: +1. **Via the `
` element's `action` prop**: This is the recommended approach for most data mutations, especially those triggered by user input, as it provides **progressive enhancement**. +2. **Programmatically with `useAction`**: For scenarios where you need to trigger an action outside of a form context. -```tsx del={1} ins={2,11,15-17} -import { action, useAction } from "@solidjs/router"; -import { action, useAction, useSubmission } from "@solidjs/router"; +### Handling Form Submissions with the `action` prop -const echo = action(async (message: string) => { - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); - return message; -}); +Solid Router extends the standard HTML `` element to accept an `action` prop, allowing you to handle your form submissions to an action. +This method provides the best user experience due to progressive enhancement. -export function MyComponent() { - const myEcho = useAction(echo); - const echoing = useSubmission(echo); +When using actions with ``: - myEcho("Hello from solid!"); +1. The `` element **must** have `method="post"`. +2. The action function will automatically receive the form's data as a [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object as its first parameter. +3. For SSR environments, you **must** provide a unique name string as the second parameter to the `action` function. + This name is used by Solid Router to uniquely identify and serialize the action across the client and server. - setTimeout(() => myEcho("This is a second submission!"), 1500); +```tsx +import { action } from "@solidjs/router"; - return

{echoing.result}

; +const addPostAction = action(async (formData: FormData) => { + const title = formData.get("title")?.toString(); + + if (!title || title.trim() === "") { + throw new Error("Post title cannot be empty."); + } + + const response = await fetch("https://api.com/posts", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ title }), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || "Failed to add post"); + } +}, "add-post"); + +function AddPostForm() { + return ( + + + + +
+ ); } ``` -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. +When this form is submitted, `addPostFormAction` will be invoked with the `FormData` containing the form values. -## Using forms to submit data +:::tip[File Uploads] -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. +If your form includes file inputs, ensure your
element has enctype="multipart/form-data" to correctly send the file data. -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. +```tsx + + + +
+``` -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. +::: +#### Passing additional data -```tsx -import { action, redirect } from "@solidjs/router"; +Sometimes, your action might need additional data that isn't part of the form's inputs. +You can pass these additional arguments using the `with` method on your action. -const isAdmin = action(async (formData: FormData) => { - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); +Arguments passed to `with` will be forwarded to your action function before the `FormData` object. - const username = formData.get("username"); +```tsx +import { action } from "@solidjs/router"; - if (username === "admin") throw redirect("/admin"); - return new Error("Invalid username"); +const updatePostAction = action(async (postId: string, formData: FormData) => { + const newTitle = formData.get("title")?.toString(); + + if (!newTitle || newTitle.trim() === "") { + throw new Error("Post title cannot be empty."); + } + + const response = await fetch( + `https://api.com/posts/${encodeURIComponent(newTitle)}`, + { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ title: newTitle }), + } + ); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || "Failed to update post"); + } }); -export function MyComponent() { - - return ( -
- - - -
- ); +function PostEditForm(props: { postId: string }) { + return ( +
+ + + +
+ ); } ``` -**Note:** If you are uploading files make sure you include `enctype="multipart/form-data"` to your `
` element. - -## Error handling - -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. - -Additionally, when using server actions, it is good practice to handle errors on the server to sanitize error messages. +Here, `updatePostAction` receives `postId` (passed via `with`), and then the `formData` from the form. 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 4c2055bcbc..f23ade50e9 100644 --- a/src/routes/solid-router/reference/data-apis/use-submission.mdx +++ b/src/routes/solid-router/reference/data-apis/use-submission.mdx @@ -13,7 +13,7 @@ const addTodoAction = action(async (formData: FormData) => { if (name.length <= 2) { throw new Error("Name must be larger than 2 characters"); } -}, "addTodo"); +}, "add-todo"); function AddTodoForm() { const submission = useSubmission(addTodoAction); 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 e5f10d17ce..9f0e06b39d 100644 --- a/src/routes/solid-router/reference/data-apis/use-submissions.mdx +++ b/src/routes/solid-router/reference/data-apis/use-submissions.mdx @@ -14,7 +14,7 @@ const addTodoAction = action(async (formData: FormData) => { throw new Error("Name must be larger than 2 characters"); } return name; -}, "addTodo"); +}, "add-todo"); export default function AddTodoForm() { const submissions = useSubmissions(addTodoAction); @@ -50,7 +50,7 @@ export default function AddTodoForm() { } ``` -:::info[Note] +:::note To access the state of the most recent submission, `useSubmission`[/solid-router/reference/data-apis/use-submission] can be used. ::: From 73da3ce96f343ec9fd44329cc4a95930e9d998eb Mon Sep 17 00:00:00 2001 From: "Amir H. Hashemi" <87268103+amirhhashemi@users.noreply.github.com> Date: Thu, 3 Jul 2025 18:03:54 +0330 Subject: [PATCH 6/6] update --- src/routes/solid-router/concepts/actions.mdx | 200 ++++++++++++++++++- src/ui/callout.tsx | 4 - 2 files changed, 190 insertions(+), 14 deletions(-) diff --git a/src/routes/solid-router/concepts/actions.mdx b/src/routes/solid-router/concepts/actions.mdx index 0f8af92cef..82237aff66 100644 --- a/src/routes/solid-router/concepts/actions.mdx +++ b/src/routes/solid-router/concepts/actions.mdx @@ -7,7 +7,6 @@ They are designed to simplify your application's data flow, ensure a consistent Actions provide several key benefits: -- **Centralized Logic**: Encapsulate the logic for data modifications in a single, reusable function. - **Integrated State Management**: Solid Router automatically tracks the execution state of an action (whether it's pending, successful, or has encountered an error), making it easy to build reactive UI feedback. - **Automatic Data Revalidation**: By default, after an action successfully completes, Solid Router revalidates any queries on the same page. This ensures your UI reflects the latest data without manual intervention. @@ -34,10 +33,10 @@ const addPostAction = action(async (title: string) => { if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.message || "Failed to add post"); + return { success: false, message: errorData.message }; } - return await response.json(); + return { success: true, data: await response.json() }; }); ``` @@ -50,7 +49,7 @@ This is crucial for Solid Router to correctly identify and re-run actions on the We'll explore this in more detail in the Handling Form Submissions section. ::: -## How to Use Actions +## Using actions Solid Router offers two primary ways to invoke an action: @@ -76,7 +75,7 @@ const addPostAction = action(async (formData: FormData) => { const title = formData.get("title")?.toString(); if (!title || title.trim() === "") { - throw new Error("Post title cannot be empty."); + return { success: false, message: "Post title cannot be empty." }; } const response = await fetch("https://api.com/posts", { @@ -89,9 +88,11 @@ const addPostAction = action(async (formData: FormData) => { if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.message || "Failed to add post"); + return { success: false, message: errorData.message }; } -}, "add-post"); + + return { success: true }; +}, "addPost"); function AddPostForm() { return ( @@ -108,7 +109,7 @@ When this form is submitted, `addPostFormAction` will be invoked with the `FormD :::tip[File Uploads] -If your form includes file inputs, ensure your element has enctype="multipart/form-data" to correctly send the file data. +If your form includes file inputs, ensure your `` element has `enctype="multipart/form-data"` to correctly send the file data. ```tsx @@ -133,7 +134,7 @@ const updatePostAction = action(async (postId: string, formData: FormData) => { const newTitle = formData.get("title")?.toString(); if (!newTitle || newTitle.trim() === "") { - throw new Error("Post title cannot be empty."); + return { success: false, message: "Post title cannot be empty." }; } const response = await fetch( @@ -149,8 +150,10 @@ const updatePostAction = action(async (postId: string, formData: FormData) => { if (!response.ok) { const errorData = await response.json(); - throw new Error(errorData.message || "Failed to update post"); + return { success: false, message: errorData.message }; } + + return { success: true }; }); function PostEditForm(props: { postId: string }) { @@ -165,3 +168,180 @@ function PostEditForm(props: { postId: string }) { ``` Here, `updatePostAction` receives `postId` (passed via `with`), and then the `formData` from the form. + +### Invoking Actions Programmatically with `useAction` + +While forms are highly recommended for progressive enhancement, there are scenarios where you might need to trigger an action directly from a Solid component, outside of a `` element. +The `useAction` primitive allows you to do this. + +`useAction` takes an action as an argument and returns a function that, when called, will invoke the action with the provided parameters. + +```tsx +import { action, useAction } from "@solidjs/router"; + +const likePostAction = action(async (postId: string) => { + await fetch(`https://api.com/posts/${encodeURIComponent(postId)}/likes`, { + method: "POST", + }); +}); + +function LikePostButton(props: { postId: string }) { + const likePost = useAction(); + + return ; +} +``` + +In this example, `likePost` is a function that can be called with arguments matching `likePostAction`. +When the button is clicked, `likePostAction` invokes for the specific post ID. + +## Query revalidation + +By default, when an action successfully finishes its execution, Solid Router will automatically revalidate all queries on the same page. +This means you typically don't have to manually refetch data after a mutation. +For example, if you add a new post using an action, any query on that page fetching a list of posts will automatically re-run and update your UI. + +```tsx +import { query, action, createAsync } from "@solidjs/router"; + +const getPostsQuery = query(async () => { + const response = await fetch("https://api.com/posts"); + return await response.json(); +}, "getPosts"); + +const addPostAction = action(async (formData: FormData) => { + const title = formData.get("title")?.toString(); + await fetch("https://api.com/posts", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ title }), + }); +}, "addPost"); + +function PostsPage() { + // This query will automatically revalidate after addPostAction completes. + const posts = createAsync(() => getPostsQuery()); + + return ( +
+

All Posts

+ {(post) =>

{post.title}

}
+ +

Add New Post

+ + + + +
+ ); +} +``` + +While automatic revalidation is convenient, there are times you need more control. +Solid Router's response helpers allow you to precisely manage which queries are revalidated, or even disable revalidation entirely for a specific action. +You'll learn more about these helpers in the next section. + +## Response helpers + +Solid Router provides special response helpers that enable your actions to explicitly dictate routing and query revalidation: + +- [`json`](/solid-router/reference/response-helpers/json): Allows customizing query revalidation behavior when the action returns something. + While it's not necessary to use this helper to return a value from an action (a plain JavaScript value also works), the `json` helper can be used when you want to return data **and** customize query revalidation. +- [`redirect`](/solid-router/reference/response-helpers/redirect): Performs a redirect. +- [`reload`](/solid-router/reference/response-helpers/reload): Revalidates queries. + +You can either `return` or `throw` the result of a response helper in order for Solid Router to handle it. +Throwing might be preferable in TypeScript environemnts since it doesn't conflict with TypeScript's type system. + +```tsx +import { action, redirect, json, reload } from "@solidjs/router"; + +// Example 1: Redirecting after a successful login +const loginAction = action(async (formData: FormData) => { + // ...Login logic + + throw redirect("/dashboard"); +}); + +// Example 2: Returning data with specific revalidation control +const savePreferencesAction = action(async (formData: FormData) => { + // ...Save preferences logic + + return json( + { success: true, message: "Preferences saved!" }, + { revalidate: ["userPreferences"] } + ); +}); + +// Example 3: Disabling revalidation +const logActivityAction = action(async (activityData: FormData) => { + // ...Log activity to server + + // Don't revalidate any queries on the current page for this action + throw reload({ revalidate: [] }); +}); +``` + +## Tracking action state + +When an action is executing, your UI often needs to reflect its current state. +Solid Router provides the `useSubmission` and `useSubmissions` primitives to track the lifecycle of your actions. + +The `useSubmission` hook returns an object with reactive values that represent the state of the _most recent_ submission for a given action. +This is perfect for displaying pending states on buttons, showing the latest error, or displaying the outcome of the action. + +```tsx +import { Show } from "solid-js"; +import { action, useSubmission } from "@solidjs/router"; + +const saveSettingsAction = action(async (formData: FormData) => { + const email = formData.get("email")?.toString(); + + if (!email || !email.includes("@")) { + throw new Error("Please enter a valid email address."); + } + + await new Promise((res) => setTimeout(res, 2000)); // Simulate API call + + return { success: true, message: "Settings saved successfully!" }; +}, "save-settings"); + +function UserSettingsForm() { + const submission = useSubmission(saveSettingsAction); + + return ( +
+ + + + + + + {(error) => ( +
+

Error: {error().message}

+ + +
+ )} +
+ + {(result) => ( +

{result().message}

+ )} +
+
+ ); +} +``` + +In this example, the form's submit button is disabled during `submission.pending`, and appropriate messages are shown based on `submission.error` or `submission.result`. +The `clear` method resets the submission state, and `retry` re-executes the last submission with its original input. + +For more details, see the [`useSubmission` API reference](/solid-router/reference/data-apis/use-submission). + +:::tip +If you need to track multiple concurrent or recent submissions for an action (e.g., a list of file uploads, a queue of items being processed), the [`useSubmissions` primitive](/solid-router/reference/data-apis/use-submissions) can be used. +::: diff --git a/src/ui/callout.tsx b/src/ui/callout.tsx index a69862a883..9878c4266a 100644 --- a/src/ui/callout.tsx +++ b/src/ui/callout.tsx @@ -101,11 +101,7 @@ export function Callout(props: CalloutProps) { -======= - ->>>>>>> 0c3b40b1 (Update info callout titles (#1207)) {props.type || "Note"} }