Skip to content

Commit fbdec7a

Browse files
committed
update
1 parent aec869e commit fbdec7a

File tree

1 file changed

+190
-10
lines changed

1 file changed

+190
-10
lines changed

src/routes/solid-router/concepts/actions.mdx

Lines changed: 190 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ They are designed to simplify your application's data flow, ensure a consistent
77

88
Actions provide several key benefits:
99

10-
- **Centralized Logic**: Encapsulate the logic for data modifications in a single, reusable function.
1110
- **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.
1211
- **Automatic Data Revalidation**: By default, after an action successfully completes, Solid Router revalidates any queries on the same page.
1312
This ensures your UI reflects the latest data without manual intervention.
@@ -34,10 +33,10 @@ const addPostAction = action(async (title: string) => {
3433

3534
if (!response.ok) {
3635
const errorData = await response.json();
37-
throw new Error(errorData.message || "Failed to add post");
36+
return { success: false, message: errorData.message };
3837
}
3938

40-
return await response.json();
39+
return { success: true, data: await response.json() };
4140
});
4241
```
4342

@@ -50,7 +49,7 @@ This is crucial for Solid Router to correctly identify and re-run actions on the
5049
We'll explore this in more detail in the Handling Form Submissions section.
5150
:::
5251

53-
## How to Use Actions
52+
## Using actions
5453

5554
Solid Router offers two primary ways to invoke an action:
5655

@@ -76,7 +75,7 @@ const addPostAction = action(async (formData: FormData) => {
7675
const title = formData.get("title")?.toString();
7776

7877
if (!title || title.trim() === "") {
79-
throw new Error("Post title cannot be empty.");
78+
return { success: false, message: "Post title cannot be empty." };
8079
}
8180

8281
const response = await fetch("https://api.com/posts", {
@@ -89,9 +88,11 @@ const addPostAction = action(async (formData: FormData) => {
8988

9089
if (!response.ok) {
9190
const errorData = await response.json();
92-
throw new Error(errorData.message || "Failed to add post");
91+
return { success: false, message: errorData.message };
9392
}
94-
}, "add-post");
93+
94+
return { success: true };
95+
}, "addPost");
9596

9697
function AddPostForm() {
9798
return (
@@ -108,7 +109,7 @@ When this form is submitted, `addPostFormAction` will be invoked with the `FormD
108109

109110
:::tip[File Uploads]
110111

111-
If your form includes file inputs, ensure your <form> element has enctype="multipart/form-data" to correctly send the file data.
112+
If your form includes file inputs, ensure your `<form>` element has `enctype="multipart/form-data"` to correctly send the file data.
112113

113114
```tsx
114115
<form action={uploadFileAction} method="post" enctype="multipart/form-data">
@@ -133,7 +134,7 @@ const updatePostAction = action(async (postId: string, formData: FormData) => {
133134
const newTitle = formData.get("title")?.toString();
134135

135136
if (!newTitle || newTitle.trim() === "") {
136-
throw new Error("Post title cannot be empty.");
137+
return { success: false, message: "Post title cannot be empty." };
137138
}
138139

139140
const response = await fetch(
@@ -149,8 +150,10 @@ const updatePostAction = action(async (postId: string, formData: FormData) => {
149150

150151
if (!response.ok) {
151152
const errorData = await response.json();
152-
throw new Error(errorData.message || "Failed to update post");
153+
return { success: false, message: errorData.message };
153154
}
155+
156+
return { success: true };
154157
});
155158

156159
function PostEditForm(props: { postId: string }) {
@@ -165,3 +168,180 @@ function PostEditForm(props: { postId: string }) {
165168
```
166169

167170
Here, `updatePostAction` receives `postId` (passed via `with`), and then the `formData` from the form.
171+
172+
### Invoking Actions Programmatically with `useAction`
173+
174+
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 `<form>` element.
175+
The `useAction` primitive allows you to do this.
176+
177+
`useAction` takes an action as an argument and returns a function that, when called, will invoke the action with the provided parameters.
178+
179+
```tsx
180+
import { action, useAction } from "@solidjs/router";
181+
182+
const likePostAction = action(async (postId: string) => {
183+
await fetch(`https://api.com/posts/${encodeURIComponent(postId)}/likes`, {
184+
method: "POST",
185+
});
186+
});
187+
188+
function LikePostButton(props: { postId: string }) {
189+
const likePost = useAction();
190+
191+
return <button onClick={() => likePost(postId)}>Like</button>;
192+
}
193+
```
194+
195+
In this example, `likePost` is a function that can be called with arguments matching `likePostAction`.
196+
When the button is clicked, `likePostAction` invokes for the specific post ID.
197+
198+
## Query revalidation
199+
200+
By default, when an action successfully finishes its execution, Solid Router will automatically revalidate all queries on the same page.
201+
This means you typically don't have to manually refetch data after a mutation.
202+
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.
203+
204+
```tsx
205+
import { query, action, createAsync } from "@solidjs/router";
206+
207+
const getPostsQuery = query(async () => {
208+
const response = await fetch("https://api.com/posts");
209+
return await response.json();
210+
}, "getPosts");
211+
212+
const addPostAction = action(async (formData: FormData) => {
213+
const title = formData.get("title")?.toString();
214+
await fetch("https://api.com/posts", {
215+
method: "POST",
216+
headers: { "Content-Type": "application/json" },
217+
body: JSON.stringify({ title }),
218+
});
219+
}, "addPost");
220+
221+
function PostsPage() {
222+
// This query will automatically revalidate after addPostAction completes.
223+
const posts = createAsync(() => getPostsQuery());
224+
225+
return (
226+
<div>
227+
<h2>All Posts</h2>
228+
<For each={posts()}>{(post) => <p>{post.title}</p>}</For>
229+
230+
<h3>Add New Post</h3>
231+
<form action={addPostAction} method="post">
232+
<input name="title" placeholder="Post title" />
233+
<button type="submit">Add Post</button>
234+
</form>
235+
</div>
236+
);
237+
}
238+
```
239+
240+
While automatic revalidation is convenient, there are times you need more control.
241+
Solid Router's response helpers allow you to precisely manage which queries are revalidated, or even disable revalidation entirely for a specific action.
242+
You'll learn more about these helpers in the next section.
243+
244+
## Response helpers
245+
246+
Solid Router provides special response helpers that enable your actions to explicitly dictate routing and query revalidation:
247+
248+
- [`json`](/solid-router/reference/response-helpers/json): Allows customizing query revalidation behavior when the action returns something.
249+
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.
250+
- [`redirect`](/solid-router/reference/response-helpers/redirect): Performs a redirect.
251+
- [`reload`](/solid-router/reference/response-helpers/reload): Revalidates queries.
252+
253+
You can either `return` or `throw` the result of a response helper in order for Solid Router to handle it.
254+
Throwing might be preferable in TypeScript environemnts since it doesn't conflict with TypeScript's type system.
255+
256+
```tsx
257+
import { action, redirect, json, reload } from "@solidjs/router";
258+
259+
// Example 1: Redirecting after a successful login
260+
const loginAction = action(async (formData: FormData) => {
261+
// ...Login logic
262+
263+
throw redirect("/dashboard");
264+
});
265+
266+
// Example 2: Returning data with specific revalidation control
267+
const savePreferencesAction = action(async (formData: FormData) => {
268+
// ...Save preferences logic
269+
270+
return json(
271+
{ success: true, message: "Preferences saved!" },
272+
{ revalidate: ["userPreferences"] }
273+
);
274+
});
275+
276+
// Example 3: Disabling revalidation
277+
const logActivityAction = action(async (activityData: FormData) => {
278+
// ...Log activity to server
279+
280+
// Don't revalidate any queries on the current page for this action
281+
throw reload({ revalidate: [] });
282+
});
283+
```
284+
285+
## Tracking action state
286+
287+
When an action is executing, your UI often needs to reflect its current state.
288+
Solid Router provides the `useSubmission` and `useSubmissions` primitives to track the lifecycle of your actions.
289+
290+
The `useSubmission` hook returns an object with reactive values that represent the state of the _most recent_ submission for a given action.
291+
This is perfect for displaying pending states on buttons, showing the latest error, or displaying the outcome of the action.
292+
293+
```tsx
294+
import { Show } from "solid-js";
295+
import { action, useSubmission } from "@solidjs/router";
296+
297+
const saveSettingsAction = action(async (formData: FormData) => {
298+
const email = formData.get("email")?.toString();
299+
300+
if (!email || !email.includes("@")) {
301+
throw new Error("Please enter a valid email address.");
302+
}
303+
304+
await new Promise((res) => setTimeout(res, 2000)); // Simulate API call
305+
306+
return { success: true, message: "Settings saved successfully!" };
307+
}, "save-settings");
308+
309+
function UserSettingsForm() {
310+
const submission = useSubmission(saveSettingsAction);
311+
312+
return (
313+
<form action={saveSettingsAction} method="post">
314+
<label for="email">Email:</label>
315+
<input id="email" name="email" type="email" />
316+
317+
<button type="submit" disabled={submission.pending}>
318+
{submission.pending ? "Saving..." : "Save Settings"}
319+
</button>
320+
321+
<Show when={submission.error}>
322+
{(error) => (
323+
<div style="color: red; margin-top: 10px;">
324+
<p>Error: {error().message}</p>
325+
<button onClick={() => submission.clear()}>Clear Error</button>
326+
<button onClick={() => submission.retry()}>Retry</button>
327+
</div>
328+
)}
329+
</Show>
330+
<Show when={submission.result}>
331+
{(result) => (
332+
<p style="color: green; margin-top: 10px;">{result().message}</p>
333+
)}
334+
</Show>
335+
</form>
336+
);
337+
}
338+
```
339+
340+
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`.
341+
The `clear` method resets the submission state, and `retry` re-executes the last submission with its original input.
342+
343+
For more details, see the [`useSubmission` API reference](/solid-router/reference/data-apis/use-submission).
344+
345+
:::tip
346+
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.
347+
:::

0 commit comments

Comments
 (0)