|
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