|
2 | 2 | title: "Actions"
|
3 | 3 | ---
|
4 | 4 |
|
5 |
| -When developing applications, it is common to need to communicate new information to the server based on user interactions. |
6 |
| -Actions are Solid Router’s solution to this problem. |
| 5 | +Actions provide a powerful and flexible mechanism for handling data mutations and side effects. |
| 6 | +They are designed to simplify your application's data flow, ensure a consistent user experience, and integrate seamlessly with Solid's reactivity. |
7 | 7 |
|
8 |
| -## What are actions? |
| 8 | +Actions provide several key benefits: |
9 | 9 |
|
10 |
| -Actions are asynchronous processing functions that allow you to submit data to your server and receive a response. |
11 |
| -They are isomorphic, meaning they can run either on the server or the client, depending on what is needed. |
12 |
| -This flexibility makes actions a powerful tool for managing and tracking data submissions. |
13 |
| - |
14 |
| -### How actions work |
15 |
| - |
16 |
| -Actions represent the server-side part of an [HTML form](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form). |
17 |
| -They handle submissions through POST requests, allowing you to easily use HTML forms to send data. |
18 |
| - |
19 |
| -When a user performs an action, such as submitting a form, the data is sent to the server for processing via an action. |
20 |
| - |
21 |
| -### Benefits of using actions |
22 |
| - |
23 |
| -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. |
24 |
| -2. **Asynchronous processing**: Actions handle data submissions asynchronously, ensuring that your application remains responsive. |
25 |
| -3. **Simplified data handling**: By using actions, the process of managing and tracking data submissions can be streamlined, reducing the complexity of your application. |
| 10 | +- **Centralized Logic**: Encapsulate the logic for data modifications in a single, reusable function. |
| 11 | +- **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. |
| 12 | +- **Automatic Data Revalidation**: By default, after an action successfully completes, Solid Router revalidates any queries on the same page. |
| 13 | + This ensures your UI reflects the latest data without manual intervention. |
| 14 | +- **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. |
26 | 15 |
|
27 | 16 | ## Creating actions
|
28 | 17 |
|
29 |
| -To create an action, use the `action` function from the `@solidjs/router` package. |
30 |
| -This function takes an asynchronous function as an argument and returns a new function that can be used to submit data. |
| 18 | +At their core, actions are **asynchronous functions** that you define using the `action` function. |
| 19 | +The [`action`](/solid-router/reference/data-apis/action) function takes your asynchronous function and returns an action object. |
| 20 | + |
| 21 | +To define an action, import the `action` function from `@solidjs/router` and pass it your asynchronous logic: |
31 | 22 |
|
32 | 23 | ```tsx
|
33 | 24 | import { action } from "@solidjs/router";
|
34 | 25 |
|
35 |
| -const echo = action(async (message: string) => { |
36 |
| - // Simulates an asynchronous operation, such as an API call |
37 |
| - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); |
38 |
| - console.log(message); |
| 26 | +const addPostAction = action(async (title: string) => { |
| 27 | + const response = await fetch("https://api.com/posts", { |
| 28 | + method: "POST", |
| 29 | + headers: { |
| 30 | + "Content-Type": "application/json", |
| 31 | + }, |
| 32 | + body: JSON.stringify({ title }), |
| 33 | + }); |
| 34 | + |
| 35 | + if (!response.ok) { |
| 36 | + const errorData = await response.json(); |
| 37 | + throw new Error(errorData.message || "Failed to add post"); |
| 38 | + } |
| 39 | + |
| 40 | + return await response.json(); |
39 | 41 | });
|
40 | 42 | ```
|
41 | 43 |
|
42 |
| -In this example, the `echo` action simulates a fetch call with a 1 second delay before logging the message to the console. |
43 |
| -The `echo` action will act as a backend, however, it can be substituted for any API provided it can be run on the client. |
44 |
| -Typically, route actions are used with some sort of solution like fetch or GraphQL. |
| 44 | +In this example, `addPostAction` handles sending a POST request to create a new post. |
| 45 | +The return value of the action can be accessed later when tracking action state. |
45 | 46 |
|
46 |
| -:::tip |
47 |
| -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. |
| 47 | +:::note[Server-Side Rendering (SSR)] |
| 48 | +When using actions with SSR, you must provide a unique name string as the second parameter to the `action` function. |
| 49 | +This is crucial for Solid Router to correctly identify and re-run actions on the server. |
| 50 | +We'll explore this in more detail in the Handling Form Submissions section. |
48 | 51 | :::
|
49 | 52 |
|
50 |
| -### Using actions |
51 |
| - |
52 |
| -To use the action, you can call it from within a component using [`useAction`](/solid-router/reference/data-apis/use-action). |
53 |
| -This returns a function that can be called with the necessary arguments to trigger the action. |
54 |
| - |
55 |
| -```tsx del={1} ins={2,9-13} |
56 |
| -import { action } from "@solidjs/router"; |
57 |
| -import { action, useAction } from "@solidjs/router"; |
58 |
| - |
59 |
| -const echo = action(async (message: string) => { |
60 |
| - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); |
61 |
| - console.log(message); |
62 |
| -}); |
63 |
| - |
64 |
| -export function MyComponent() { |
65 |
| - const myEcho = useAction(echo); |
66 |
| - |
67 |
| - myEcho("Hello from Solid!"); |
68 |
| -} |
69 |
| -``` |
70 |
| - |
71 |
| -In this component, `useAction` is used to get a reference to the `echo` action. |
72 |
| -The action is then called with the message `"Hello from Solid!"`, which will be logged to the console after a 1 second delay. |
73 |
| - |
74 |
| -### Returning data from actions |
| 53 | +## How to Use Actions |
75 | 54 |
|
76 |
| -In many cases, after submitting data, the server sends some data back as well. |
77 |
| -This may be in the form of an error message if something has failed or the results of a successful operation. |
78 |
| -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. |
| 55 | +Solid Router offers two primary ways to invoke an action: |
79 | 56 |
|
80 |
| -To access the action's result, you must pass the action to `useSubmission`: |
| 57 | +1. **Via the `<form>` element's `action` prop**: This is the recommended approach for most data mutations, especially those triggered by user input, as it provides **progressive enhancement**. |
| 58 | +2. **Programmatically with `useAction`**: For scenarios where you need to trigger an action outside of a form context. |
81 | 59 |
|
82 |
| -```tsx del={1} ins={2,11,15-17} |
83 |
| -import { action, useAction } from "@solidjs/router"; |
84 |
| -import { action, useAction, useSubmission } from "@solidjs/router"; |
| 60 | +### Handling Form Submissions with the `action` prop |
85 | 61 |
|
86 |
| -const echo = action(async (message: string) => { |
87 |
| - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); |
88 |
| - return message; |
89 |
| -}); |
| 62 | +Solid Router extends the standard HTML `<form>` element to accept an `action` prop, allowing you to handle your form submissions to an action. |
| 63 | +This method provides the best user experience due to progressive enhancement. |
90 | 64 |
|
91 |
| -export function MyComponent() { |
92 |
| - const myEcho = useAction(echo); |
93 |
| - const echoing = useSubmission(echo); |
| 65 | +When using actions with `<form>`: |
94 | 66 |
|
95 |
| - myEcho("Hello from solid!"); |
| 67 | +1. The `<form>` element **must** have `method="post"`. |
| 68 | +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. |
| 69 | +3. For SSR environments, you **must** provide a unique name string as the second parameter to the `action` function. |
| 70 | + This name is used by Solid Router to uniquely identify and serialize the action across the client and server. |
96 | 71 |
|
97 |
| - setTimeout(() => myEcho("This is a second submission!"), 1500); |
| 72 | +```tsx |
| 73 | +import { action } from "@solidjs/router"; |
98 | 74 |
|
99 |
| - return <p>{echoing.result}</p>; |
| 75 | +const addPostAction = action(async (formData: FormData) => { |
| 76 | + const title = formData.get("title")?.toString(); |
| 77 | + |
| 78 | + if (!title || title.trim() === "") { |
| 79 | + throw new Error("Post title cannot be empty."); |
| 80 | + } |
| 81 | + |
| 82 | + const response = await fetch("https://api.com/posts", { |
| 83 | + method: "POST", |
| 84 | + headers: { |
| 85 | + "Content-Type": "application/json", |
| 86 | + }, |
| 87 | + body: JSON.stringify({ title }), |
| 88 | + }); |
| 89 | + |
| 90 | + if (!response.ok) { |
| 91 | + const errorData = await response.json(); |
| 92 | + throw new Error(errorData.message || "Failed to add post"); |
| 93 | + } |
| 94 | +}, "add-post"); |
| 95 | + |
| 96 | +function AddPostForm() { |
| 97 | + return ( |
| 98 | + <form action={addPostAction} method="post"> |
| 99 | + <label for="title">Post Title:</label> |
| 100 | + <input id="title" name="title" placeholder="Enter post title" /> |
| 101 | + <button type="submit">Create Post</button> |
| 102 | + </form> |
| 103 | + ); |
100 | 104 | }
|
101 | 105 | ```
|
102 | 106 |
|
103 |
| -Using `useSubmission` leaves the implementation details of how you trigger `echo` up to you. |
104 |
| -When handling user inputs, for example, it is better to use a `form` for a multitude of reasons. |
| 107 | +When this form is submitted, `addPostFormAction` will be invoked with the `FormData` containing the form values. |
105 | 108 |
|
106 |
| -## Using forms to submit data |
| 109 | +:::tip[File Uploads] |
107 | 110 |
|
108 |
| -When submitting data with actions, it is recommended to use HTML forms. |
109 |
| -These forms can be used prior to JavaScript loading, which creates instantly interactive applications. |
110 |
| -This also inherently provides accessibility benefits, saving the time of designing a custom UI library that may not have these benefits. |
| 111 | +If your form includes file inputs, ensure your <form> element has enctype="multipart/form-data" to correctly send the file data. |
111 | 112 |
|
112 |
| -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). |
113 |
| -To use actions with forms, pass the action to the `action` property of your form. |
114 |
| -This creates progressively enhanced forms that work even when JavaScript is disabled. |
| 113 | +```tsx |
| 114 | +<form action={uploadFileAction} method="post" enctype="multipart/form-data"> |
| 115 | + <input type="file" name="myFile" /> |
| 116 | + <button type="submit">Upload</button> |
| 117 | +</form> |
| 118 | +``` |
115 | 119 |
|
116 |
| -If you do not return a `Response` from your action, the user will stay on the same page and responses will be re-triggered. |
117 |
| -Using a `redirect` can tell the browser to navigate to a new page. |
| 120 | +::: |
118 | 121 |
|
| 122 | +#### Passing additional data |
119 | 123 |
|
120 |
| -```tsx |
121 |
| -import { action, redirect } from "@solidjs/router"; |
| 124 | +Sometimes, your action might need additional data that isn't part of the form's inputs. |
| 125 | +You can pass these additional arguments using the `with` method on your action. |
122 | 126 |
|
123 |
| -const isAdmin = action(async (formData: FormData) => { |
124 |
| - await new Promise((resolve, reject) => setTimeout(resolve, 1000)); |
| 127 | +Arguments passed to `with` will be forwarded to your action function before the `FormData` object. |
125 | 128 |
|
126 |
| - const username = formData.get("username"); |
| 129 | +```tsx |
| 130 | +import { action } from "@solidjs/router"; |
127 | 131 |
|
128 |
| - if (username === "admin") throw redirect("/admin"); |
129 |
| - return new Error("Invalid username"); |
| 132 | +const updatePostAction = action(async (postId: string, formData: FormData) => { |
| 133 | + const newTitle = formData.get("title")?.toString(); |
| 134 | + |
| 135 | + if (!newTitle || newTitle.trim() === "") { |
| 136 | + throw new Error("Post title cannot be empty."); |
| 137 | + } |
| 138 | + |
| 139 | + const response = await fetch( |
| 140 | + `https://api.com/posts/${encodeURIComponent(newTitle)}`, |
| 141 | + { |
| 142 | + method: "PUT", |
| 143 | + headers: { |
| 144 | + "Content-Type": "application/json", |
| 145 | + }, |
| 146 | + body: JSON.stringify({ title: newTitle }), |
| 147 | + } |
| 148 | + ); |
| 149 | + |
| 150 | + if (!response.ok) { |
| 151 | + const errorData = await response.json(); |
| 152 | + throw new Error(errorData.message || "Failed to update post"); |
| 153 | + } |
130 | 154 | });
|
131 | 155 |
|
132 |
| -export function MyComponent() { |
133 |
| - |
134 |
| - return ( |
135 |
| - <form action={isAdmin} method="post"> |
136 |
| - <label for="username">Username:</label> |
137 |
| - <input type="text" name="username" /> |
138 |
| - <input type="submit" value="submit" /> |
139 |
| - </form> |
140 |
| - ); |
| 156 | +function PostEditForm(props: { postId: string }) { |
| 157 | + return ( |
| 158 | + <form action={updatePostAction.with(props.postId)} method="post"> |
| 159 | + <label for="title">Title:</label> |
| 160 | + <input id="title" name="title" placeholder="New title" /> |
| 161 | + <button type="submit">Update Post</button> |
| 162 | + </form> |
| 163 | + ); |
141 | 164 | }
|
142 | 165 | ```
|
143 | 166 |
|
144 |
| -**Note:** If you are uploading files make sure you include `enctype="multipart/form-data"` to your `<form>` element. |
145 |
| - |
146 |
| -## Error handling |
147 |
| - |
148 |
| -Rather than throwing errors, it is recommended to return them from actions. |
149 |
| -This helps with the typing of submissions that would be used with `useSubmission`. |
150 |
| -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. |
151 |
| - |
152 |
| -Additionally, when using server actions, it is good practice to handle errors on the server to sanitize error messages. |
| 167 | +Here, `updatePostAction` receives `postId` (passed via `with`), and then the `formData` from the form. |
0 commit comments