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. 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 f51ca4db20..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,160 +2,83 @@ 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 retrieves the state of the most recent submission for a given 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}

+ + +
+ )} +
+
+ ); } ``` -:::info -Learn more about actions in the [`action`](/solid-router/reference/data-apis/action) docs. +:::info[Note] +To access the state of 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()}
} -
+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 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 submission: {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 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. -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. + 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. 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 b17232eae5..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})`} + + + )} + +
+ )} +
+
+ ); } ``` -:::info -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.