Guides

Data mutation

Edit this page

This guide provides practical examples of using actions to mutate data in SolidStart.


Handling form submission

To handle <form> submissions with an action:

  1. Ensure the action has a unique name. See the Action API reference for more information.
  2. Pass the action to the <form> element using the action prop.
  3. Ensure the <form> element uses the post method for submission.
  4. Use the FormData object in the action to extract field data using the navite FormData methods.
src/routes/index.tsx
import { action } from "@solidjs/router";
const addPost = action(async (formData: FormData) => {
const title = formData.get("title") as string;
await fetch("https://my-api.com/posts", {
method: "POST",
body: JSON.stringify({ title }),
});
}, "addPost");
export default function Page() {
return (
<form action={addPost} method="post">
<input name="title" />
<button>Add Post</button>
</form>
);
}

Passing additional arguments

To pass additional arguments to your action, use the with method:

src/routes/index.tsx
import { action } from "@solidjs/router";
const addPost = action(async (userId: number, formData: FormData) => {
const title = formData.get("title") as string;
await fetch("https://my-api.com/posts", {
method: "POST",
body: JSON.stringify({ userId, title }),
});
}, "addPost");
export default function Page() {
const userId = 1;
return (
<form action={addPost.with(userId)} method="post">
<input name="title" />
<button>Add Post</button>
</form>
);
}

Showing pending UI

To display a pending UI during action execution:

  1. Import useSubmission from @solidjs/router.
  2. Call useSubmission with your action, and use the returned pending property to display pending UI.
src/routes/index.tsx
import { action, useSubmission } from "@solidjs/router";
const addPost = action(async (formData: FormData) => {
const title = formData.get("title") as string;
await fetch("https://my-api.com/posts", {
method: "POST",
body: JSON.stringify({ title }),
});
}, "addPost");
export default function Page() {
const submission = useSubmission(addPost);
return (
<form action={addPost} method="post">
<input name="title" />
<button disabled={submission.pending}>
{submission.pending ? "Adding..." : "Add Post"}
</button>
</form>
);
}

Handling errors

To handle errors that occur within an action:

  1. Import useSubmission from @solidjs/router.
  2. Call useSubmission with your action, and use the returned error property to handle the error.
src/routes/index.tsx
import { Show } from "solid-js";
import { action, useSubmission } from "@solidjs/router";
const addPost = action(async (formData: FormData) => {
const title = formData.get("title") as string;
await fetch("https://my-api.com/posts", {
method: "POST",
body: JSON.stringify({ title }),
});
}, "addPost");
export default function Page() {
const submission = useSubmission(addPost);
return (
<form action={addPost} method="post">
<Show when={submission.error}>
<p>{submission.error.message}</p>
</Show>
<input name="title" />
<button>Add Post</button>
</form>
);
}

Validating form fields

To validate form fields in an action:

  1. Add validation logic in your action and return validation errors if the data is invalid.
  2. Import useSubmission from @solidjs/router.
  3. Call useSubmission with your action, and use the returned result property to handle the errors.
src/routes/index.tsx
import { Show } from "solid-js";
import { action, useSubmission } from "@solidjs/router";
const addPost = action(async (formData: FormData) => {
const title = formData.get("title") as string;
if (!title || title.length < 2) {
return {
error: "Title must be at least 2 characters",
};
}
await fetch("https://my-api.com/posts", {
method: "POST",
body: JSON.stringify({ title }),
});
}, "addPost");
export default function Page() {
const submission = useSubmission(addPost);
return (
<form action={addPost} method="post">
<input name="title" />
<Show when={submission.result?.error}>
<p>{submission.result?.error}</p>
</Show>
<button>Add Post</button>
</form>
);
}

Showing optimistic UI

To update the UI before the server responds:

  1. Import useSubmission from @solidjs/router.
  2. Call useSubmission with your action, and use the returned pending and input properties to display optimistic UI.
src/routes/index.tsx
import { For, Show } from "solid-js";
import { action, useSubmission, query, createAsync } from "@solidjs/router";
const getPosts = query(async () => {
const posts = await fetch("https://my-api.com/blog");
return await posts.json();
}, "posts");
const addPost = action(async (formData: FormData) => {
const title = formData.get("title");
await fetch("https://my-api.com/posts", {
method: "POST",
body: JSON.stringify({ title }),
});
}, "addPost");
export default function Page() {
const posts = createAsync(() => getPosts());
const submission = useSubmission(addPost);
return (
<main>
<form action={addPost} method="post">
<input name="title" />
<button>Add Post</button>
</form>
<ul>
<For each={posts()}>{(post) => <li>{post.title}</li>}</For>
<Show when={submission.pending}>
{submission.input?.[0]?.get("title")?.toString()}
</Show>
</ul>
</main>
);
}

Redirecting

To redirect users to a different route within an action:

  1. Import redirect from @solidjs/router.
  2. Call redirect with the route you want to navigate to, and throw its response.
src/routes/index.tsx
import { action, redirect } from "@solidjs/router";
const addPost = action(async (formData: FormData) => {
const title = formData.get("title") as string;
const response = await fetch("https://my-api.com/posts", {
method: "POST",
body: JSON.stringify({ title }),
});
const post = await response.json();
throw redirect(`/posts/${post.id}`);
}, "addPost");
export default function Page() {
return (
<form action={addPost} method="post">
<input name="title" />
<button>Add Post</button>
</form>
);
}

Using a database or an ORM

To safely interact with your database or ORM in an action, ensure it's server-only by adding "use server" as the first line of your action:

src/routes/index.tsx
import { action } from "@solidjs/router";
import { db } from "~/lib/db";
const addPost = action(async (formData: FormData) => {
"use server";
const title = formData.get("title") as string;
await db.insert("posts").values({ title });
}, "addPost");
export default function Page() {
return (
<form action={addPost} method="post">
<input name="title" />
<button>Add Post</button>
</form>
);
}

Invoking an action programmatically

To programmatically invoke an action:

  1. Import useAction from @solidjs/router.
  2. Call useAction with your action, and use the returned function to invoke the action.
src/routes/index.tsx
import { createSignal } from "solid-js";
import { action, useAction } from "@solidjs/router";
const addPost = action(async (title: string) => {
await fetch("https://my-api.com/posts", {
method: "POST",
body: JSON.stringify({ title }),
});
}, "addPost");
export default function Page() {
const [title, setTitle] = createSignal("");
const addPostAction = useAction(addPost);
return (
<div>
<input value={title()} onInput={(e) => setTitle(e.target.value)} />
<button onClick={() => addPostAction(title())}>Add Post</button>
</div>
);
}
Report an issue with this page