Skip to content

Commit 9ad48e6

Browse files
committed
draft
1 parent d2978b5 commit 9ad48e6

File tree

3 files changed

+278
-1
lines changed

3 files changed

+278
-1
lines changed

src/routes/solid-start/data.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"index.mdx",
55
"getting-started.mdx",
66
"building-your-application",
7-
"advanced"
7+
"advanced",
8+
"guides"
89
]
910
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
---
2+
title: "Actions and Mutations"
3+
---
4+
5+
This guide provides practical examples of using actions to mutate data in SolidStart.
6+
7+
## Handling form submission
8+
9+
SolidStart extends the HTML [`<form>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form) element to allow actions to be invoked with the `action` prop. You can use it to handle form submissions:
10+
11+
1. Create an action with a unique name
12+
2. Pass your action to the `<form>` element in the `action` prop
13+
3. Make sure the `<form>` element uses the `post` method
14+
15+
```tsx {3-9} {13} title="src/routes/index.tsx"
16+
import { action } from "@solidjs/router";
17+
18+
const addPost = action(async (formData: FormData) => {
19+
const title = formData.get("title");
20+
await fetch("https://my-api.com/posts", {
21+
method: "POST",
22+
body: JSON.stringify({ title }),
23+
});
24+
}, "addPost");
25+
26+
export default function Page() {
27+
return (
28+
<form action={addPost} method="post">
29+
<input name="title" />
30+
<button>Add Post</button>
31+
</form>
32+
);
33+
}
34+
```
35+
36+
When invoked in a form, the action automatically receives the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) object. You can extract the field data using the native `FormData` methods.
37+
38+
If your action accepts typed data, you can use the `with` method to pass custom data to the action:
39+
40+
```tsx {14} title="src/routes/index.tsx"
41+
import { createSignal } from "solid-js";
42+
import { action } from "@solidjs/router";
43+
44+
const addPost = action(async (title: string) => {
45+
await fetch("https://my-api.com/posts", {
46+
method: "POST",
47+
body: JSON.stringify({ title }),
48+
});
49+
}, "addPost");
50+
51+
export default function Page() {
52+
const [title, setTitle] = createSignal("");
53+
return (
54+
<form action={addPost.with(title())} method="post">
55+
<input value={title()} onChange={(e) => setTitle(e.target.value)} />
56+
<button>Add Post</button>
57+
</form>
58+
);
59+
}
60+
```
61+
62+
Note that the action takes a `string` as its parameter rather than a `FormData`.
63+
64+
## Showing loading UI
65+
66+
To display a loading UI while the action is being executed, you can use the [`useSubmission`](/solid-router/reference/data-apis/use-submission) primitive:
67+
68+
1. Import `useSubmission` from `@solidjs/router`
69+
2. Call `useSubmission` with your action
70+
3. Use the `pending` property to show a loading state
71+
72+
```tsx {12} {16-18} title="src/routes/index.tsx"
73+
import { action, useSubmission } from "@solidjs/router";
74+
75+
const addPost = action(async (formData: FormData) => {
76+
const title = formData.get("title");
77+
await fetch("https://my-api.com/posts", {
78+
method: "POST",
79+
body: JSON.stringify({ title }),
80+
});
81+
}, "addPost");
82+
83+
export default function Page() {
84+
const submission = useSubmission(addPost);
85+
return (
86+
<form action={addPost} method="post">
87+
<input name="title" />
88+
<button disabled={submission.pending}>
89+
{submission.pending ? "Adding..." : "Add Post"}
90+
</button>
91+
</form>
92+
);
93+
}
94+
```
95+
96+
## Handling errors
97+
98+
If an error occurs within an action, the error can be accessed using the [`useSubmission`](/solid-router/reference/data-apis/use-submission) primitive:
99+
100+
1. Import `useSubmission` from `@solidjs/router`
101+
2. Call `useSubmission` with your action
102+
3. Use the `error` property to show the error message
103+
104+
```tsx {13} {16-18} title="src/routes/index.tsx"
105+
import { Show } from "solid-js";
106+
import { action, useSubmission } from "@solidjs/router";
107+
108+
const addPost = action(async (formData: FormData) => {
109+
const title = formData.get("title");
110+
await fetch("https://my-api.com/posts", {
111+
method: "POST",
112+
body: JSON.stringify({ title }),
113+
});
114+
}, "addPost");
115+
116+
export default function Page() {
117+
const submission = useSubmission(addPost);
118+
return (
119+
<form action={addPost} method="post">
120+
<Show when={submission.error}>
121+
<p>{submission.error.message}</p>
122+
</Show>
123+
<input name="title" />
124+
<button>Add Post</button>
125+
</form>
126+
);
127+
}
128+
```
129+
130+
## Validating form fields
131+
132+
To validate form fields before submission, you can use the [`useSubmission`](/solid-router/reference/data-apis/use-submission) primitive:
133+
134+
1. Add validation logic in your action and return validation errors if the data is invalid
135+
2. Import `useSubmission` from `@solidjs/router`
136+
3. Call `useSubmission` with your action
137+
4. Display validation errors using the `result` property
138+
139+
```tsx {6-10} {17} {22-24} title="src/routes/index.tsx"
140+
import { Show } from "solid-js";
141+
import { action, useSubmission } from "@solidjs/router";
142+
143+
const addPost = action(async (formData: FormData) => {
144+
const title = formData.get("title") as string;
145+
if (!title || title.length < 2) {
146+
return {
147+
error: "Title must be at least 2 characters",
148+
};
149+
}
150+
await fetch("https://my-api.com/posts", {
151+
method: "POST",
152+
body: JSON.stringify({ title }),
153+
});
154+
}, "addPost");
155+
156+
export default function Page() {
157+
const submission = useSubmission(addPost);
158+
return (
159+
<form action={addPost} method="post">
160+
<input name="title" />
161+
<Show when={submission.result?.error}>
162+
<p>{submission.result?.error}</p>
163+
</Show>
164+
<button>Add Post</button>
165+
</form>
166+
);
167+
}
168+
```
169+
170+
## Using a database or an ORM
171+
172+
To safely interact with your database or ORM in an action, ensure the action is server-only to prevent exposing your database credentials to the client. You can do this by adding [`"use server"`](/solid-start/reference/server/use-server) as the first line of your action:
173+
174+
```tsx {5} title="src/routes/index.tsx"
175+
import { action } from "@solidjs/router";
176+
import { db } from "~/lib/db";
177+
178+
const addPost = action(async (formData: FormData) => {
179+
"use server";
180+
const title = formData.get("title");
181+
await db.insert("posts").values({ title });
182+
}, "addPost");
183+
184+
export default function Page() {
185+
return (
186+
<form action={addPost} method="post">
187+
<input name="title" />
188+
<button>Add Post</button>
189+
</form>
190+
);
191+
}
192+
```
193+
194+
## Showing optimistic UI
195+
196+
To update the UI before the server responds, you can use the [`useSubmission`](/solid-router/reference/data-apis/use-submission) primitive:
197+
198+
1. Import `useSubmission` from `@solidjs/router`
199+
2. Call `useSubmission` with your action
200+
3. Use the `pending` and `input` properties to display optimistic UI
201+
202+
```tsx {19} {28-30} title="src/routes/index.tsx"
203+
import { For, Show } from "solid-js";
204+
import { action, useSubmission, query, createAsync } from "@solidjs/router";
205+
206+
const getPosts = query(async () => {
207+
const posts = await fetch("https://my-api.com/blog");
208+
return await posts.json();
209+
}, "posts");
210+
211+
const addPost = action(async (formData: FormData) => {
212+
const title = formData.get("title");
213+
await fetch("https://my-api.com/posts", {
214+
method: "POST",
215+
body: JSON.stringify({ title }),
216+
});
217+
}, "addPost");
218+
219+
export default function Page() {
220+
const posts = createAsync(() => getPosts());
221+
const submission = useSubmission(addPost);
222+
return (
223+
<main>
224+
<form action={addPost} method="post">
225+
<input name="title" />
226+
<button>Add Post</button>
227+
</form>
228+
<ul>
229+
<For each={posts()}>{(post) => <li>{post.title}</li>}</For>
230+
<Show when={submission.pending}>
231+
{submission.input?.[0]?.get("title")?.toString()}
232+
</Show>
233+
</ul>
234+
</main>
235+
);
236+
}
237+
```
238+
239+
<Callout type="info" title="Multiple Submissions">
240+
If you want to display optimistic UI for multiple concurrent submissions, you can use the [`useSubmissions`](/solid-router/reference/data-apis/use-submissions) primitive.
241+
</Callout>
242+
243+
## Calling an action manually
244+
245+
If you don't want to use a `<form>` element, you can use the `useAction` primitive to manually call an action:
246+
247+
1. Import `useAction` from `@solidjs/router`
248+
2. Call `useAction` with your action
249+
3. Use the returned function to trigger the action
250+
251+
```tsx {13} {17} title="src/routes/index.tsx"
252+
import { createSignal } from "solid-js";
253+
import { action, useAction } from "@solidjs/router";
254+
255+
const addPost = action(async (title: string) => {
256+
await fetch("https://my-api.com/posts", {
257+
method: "POST",
258+
body: JSON.stringify({ title }),
259+
});
260+
}, "addPost");
261+
262+
export default function Page() {
263+
const [title, setTitle] = createSignal("");
264+
const addPostAction = useAction(addPost);
265+
return (
266+
<div>
267+
<input value={title()} onInput={(e) => setTitle(e.target.value)} />
268+
<button onClick={() => addPostAction(title())}>Add Post</button>
269+
</div>
270+
);
271+
}
272+
```
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"title": "Guides",
3+
"pages": ["actions-and-mutations.mdx"]
4+
}

0 commit comments

Comments
 (0)