solidstart-data-mutation
Guides implementation of SolidStart form submissions with error handling and state management.
Install
mkdir -p .claude/skills/solidstart-data-mutation && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14379" && unzip -o skill.zip -d .claude/skills/solidstart-data-mutation && rm skill.zipInstalls to .claude/skills/solidstart-data-mutation
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
SolidStart data mutation: form submissions with actions, validation, error handling, pending states, optimistic UI, redirects, database operations, programmatic triggers.Key capabilities
- →Handle form submissions with SolidStart actions
- →Pass additional arguments to actions using `.with()`
- →Track submission states for pending UI
- →Display errors from failed actions
- →Validate form fields within actions
How it works
The skill defines patterns for handling data mutations in SolidStart, using actions to process form submissions, validate data, manage pending states, and handle errors.
Inputs & outputs
When to use solidstart-data-mutation
- →Add form action
- →Handle form error state
- →Implement optimistic update
About this skill
SolidStart Data Mutation
Complete guide to handling data mutations in SolidStart using actions, forms, validation, and error handling.
Basic Form Submission
Actions handle form submissions. Forms must use method="post":
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>
);
}
Requirements:
- Action must have unique name (second parameter)
- Form must use
method="post" - Action receives
FormDataas first parameter - Use
FormData.get()to extract field values
Passing Additional Arguments
Use .with() to pass additional arguments to actions:
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
Use useSubmission to track submission state and show pending UI:
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>
);
}
Submission properties:
pending- Boolean indicating if action is runningresult- Successful return valueerror- Error throwninput- Reactive input dataclear()- Clear submission stateretry()- Re-execute with same input
Handling Errors
Display errors from failed actions:
import { Show } from "solid-js";
import { action, useSubmission } 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 }),
});
if (!response.ok) {
throw new Error("Failed to add post");
}
}, "addPost");
export default function Page() {
const submission = useSubmission(addPost);
return (
<form action={addPost} method="post">
<Show when={submission.error}>
<p class="error">{submission.error.message}</p>
<button onClick={() => submission.retry()}>Retry</button>
</Show>
<input name="title" />
<button>Add Post</button>
</form>
);
}
Validating Form Fields
Return validation errors from actions and display them:
import { Show } from "solid-js";
import { action, useSubmission } from "@solidjs/router";
const addPost = action(async (formData: FormData) => {
const title = formData.get("title") as string;
// Validate
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 }),
});
return { success: true };
}, "addPost");
export default function Page() {
const submission = useSubmission(addPost);
return (
<form action={addPost} method="post">
<input name="title" />
<Show when={submission.result?.error}>
<p class="error">{submission.result.error}</p>
</Show>
<button>Add Post</button>
</form>
);
}
Validation pattern:
- Return error object from action (don't throw)
- Check
submission.result?.errorin UI - Action continues execution if validation passes
Optimistic UI
Show expected result immediately before server responds. See solidstart-optimistic-ui rule for detailed patterns.
Basic pattern with useSubmission:
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") as string;
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}>
<li>{submission.input?.[0]?.get("title")?.toString()} (pending)</li>
</Show>
</ul>
</main>
);
}
For multiple concurrent submissions, use useSubmissions (see solidstart-optimistic-ui rule).
Redirecting After Mutation
Redirect users after successful mutation:
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 to navigate
throw redirect(`/posts/${post.id}`);
}, "addPost");
Important: Must throw redirect(), not return it.
Using Database or ORM
Mark actions with "use server" to safely access database:
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");
Best practices:
- Always use
"use server"for database operations - Keeps API keys and database credentials secure
- Runs exclusively on server
- Can be called from client (automatically transformed to RPC)
Programmatic Action Triggers
Use useAction to trigger actions programmatically (not just from forms):
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);
const handleSubmit = async () => {
await addPostAction(title());
setTitle(""); // Clear input
};
return (
<div>
<input
value={title()}
onInput={(e) => setTitle(e.target.value)}
/>
<button onClick={handleSubmit}>Add Post</button>
</div>
);
}
Use cases:
- Custom form handling (not using native
<form>) - Button clicks that trigger mutations
- Complex validation before submission
- Multiple actions in sequence
Complete Example: Form with Validation and Error Handling
import { Show } from "solid-js";
import { action, useSubmission, redirect } from "@solidjs/router";
const createUser = action(async (formData: FormData) => {
"use server";
const email = formData.get("email") as string;
const name = formData.get("name") as string;
// Validation
if (!email || !email.includes("@")) {
return { error: "Invalid email address" };
}
if (!name || name.length < 2) {
return { error: "Name must be at least 2 characters" };
}
// Database operation
const user = await db.users.create({ email, name });
// Redirect on success
throw redirect(`/users/${user.id}`);
}, "createUser");
export default function CreateUserPage() {
const submission = useSubmission(createUser);
return (
<form action={createUser} method="post">
<input name="email" type="email" />
<input name="name" />
<Show when={submission.result?.error}>
<p class="error">{submission.result.error}</p>
</Show>
<Show when={submission.error}>
<p class="error">Error: {submission.error.message}</p>
<button onClick={() => submission.retry()}>Retry</button>
</Show>
<button disabled={submission.pending}>
{submission.pending ? "Creating..." : "Create User"}
</button>
</form>
);
}
Best Practices
- Always name actions: Second parameter to
action()must be unique - Use
"use server"for database: Keeps credentials secure - Track submissions: Use
useSubmissionfor better UX (pending, errors) - Validate in actions: Return error objects, don't throw for validation errors
- Handle errors: Show error messages and provide retry options
- Use
.with()for additional args: When forms need extra context - Throw redirects: Must throw, not return, redirect responses
- Optimistic UI: Use
useSubmissionsfor multiple concurrent mutations - Programmatic triggers: Use
useActionwhen not using native forms
Common Patterns
File Uploads
const uploadFile = action(async (formData: FormData) => {
"use server";
const file = formData.get("file") as File;
// Handle file upload
}, "uploadFile");
<form action={uploadF
---
*Content truncated.*
When not to use it
- →When not using SolidStart framework
- →When server-side actions are not desired
- →When optimistic UI updates are not a requirement
Limitations
- →Actions must have a unique name
- →Forms must use `method="post"`
- →Redirects must be thrown, not returned
How it compares
This skill provides a structured, framework-specific approach to data mutation in SolidStart, integrating form handling, validation, and UI feedback directly into server-side actions, unlike generic client-side form processing.
Compared to similar skills
solidstart-data-mutation side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| solidstart-data-mutation (this skill) | 0 | 6mo | No flags | Intermediate |
| shopify-development | 12 | 6mo | Review | Intermediate |
| nuxt | 19 | 6mo | No flags | Intermediate |
| tanstack-query | 7 | 2mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
More by majiayu000
View all by majiayu000 →You might also like
shopify-development
davila7
Build Shopify apps, extensions, themes using GraphQL Admin API, Shopify CLI, Polaris UI, and Liquid. TRIGGER: "shopify", "shopify app", "checkout extension", "admin extension", "POS extension", "shopify theme", "liquid template", "polaris", "shopify graphql", "shopify webhook", "shopify billing", "app subscription", "metafields", "shopify functions"
nuxt
antfu
Nuxt full-stack Vue framework with SSR, auto-imports, and file-based routing. Use when working with Nuxt apps, server routes, useFetch, middleware, or hybrid rendering.
tanstack-query
exceptionless
Data fetching and caching with TanStack Query in Svelte. Query patterns, mutations, cache invalidation, WebSocket-driven updates, and optimistic updates. Keywords: createQuery, createMutation, TanStack Query, query keys, cache invalidation, optimistic updates, refetch, stale time, @exceptionless/fetchclient, WebSocket
telegram-dev
2025Emma
Telegram 生态开发全栈指南 - 涵盖 Bot API、Mini Apps (Web Apps)、MTProto 客户端开发。包括消息处理、支付、内联模式、Webhook、认证、存储、传感器 API 等完整开发资源。
yjs
EpicenterHQ
Yjs CRDT patterns, shared types, conflict resolution, and meta data structures. Use when building collaborative apps with Yjs, handling Y.Map/Y.Array/Y.Text, implementing drag-and-drop reordering, or optimizing document storage.
bun-development
davila7
Modern JavaScript/TypeScript development with Bun runtime. Covers package management, bundling, testing, and migration from Node.js. Use when working with Bun, optimizing JS/TS development speed, or migrating from Node.js to Bun.