solidstart-data-mutation
SolidStart data mutation: form submissions with actions, validation, error handling, pending states, optimistic UI, redirects, database operations, programmatic triggers.
Best use case
solidstart-data-mutation is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
SolidStart data mutation: form submissions with actions, validation, error handling, pending states, optimistic UI, redirects, database operations, programmatic triggers.
Teams using solidstart-data-mutation should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/solidstart-data-mutation/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How solidstart-data-mutation Compares
| Feature / Agent | solidstart-data-mutation | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
SolidStart data mutation: form submissions with actions, validation, error handling, pending states, optimistic UI, redirects, database operations, programmatic triggers.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# 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"`:
```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>
);
}
```
**Requirements:**
- Action must have unique name (second parameter)
- Form must use `method="post"`
- Action receives `FormData` as first parameter
- Use `FormData.get()` to extract field values
## Passing Additional Arguments
Use `.with()` to pass additional arguments to actions:
```tsx
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:
```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>
);
}
```
**Submission properties:**
- `pending` - Boolean indicating if action is running
- `result` - Successful return value
- `error` - Error thrown
- `input` - Reactive input data
- `clear()` - Clear submission state
- `retry()` - Re-execute with same input
## Handling Errors
Display errors from failed actions:
```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;
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:
```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;
// 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?.error` in 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`:
```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") 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:
```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 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:
```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");
```
**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):
```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);
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
```tsx
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
1. **Always name actions**: Second parameter to `action()` must be unique
2. **Use `"use server"` for database**: Keeps credentials secure
3. **Track submissions**: Use `useSubmission` for better UX (pending, errors)
4. **Validate in actions**: Return error objects, don't throw for validation errors
5. **Handle errors**: Show error messages and provide retry options
6. **Use `.with()` for additional args**: When forms need extra context
7. **Throw redirects**: Must throw, not return, redirect responses
8. **Optimistic UI**: Use `useSubmissions` for multiple concurrent mutations
9. **Programmatic triggers**: Use `useAction` when not using native forms
## Common Patterns
### File Uploads
```tsx
const uploadFile = action(async (formData: FormData) => {
"use server";
const file = formData.get("file") as File;
// Handle file upload
}, "uploadFile");
<form action={uploadFile} method="post" enctype="multipart/form-data">
<input name="file" type="file" />
<button>Upload</button>
</form>
```
### Multiple Actions in Sequence
```tsx
const saveDraft = useAction(saveDraftAction);
const publish = useAction(publishAction);
const handlePublish = async () => {
await saveDraft(data);
await publish(data.id);
};
```
### Conditional Redirects
```tsx
const updatePost = action(async (formData: FormData) => {
"use server";
const post = await db.posts.update(formData);
if (post.published) {
throw redirect(`/posts/${post.id}`);
} else {
throw redirect(`/posts/${post.id}/edit`);
}
}, "updatePost");
```Related Skills
write-data-type-ref
Write a reference documentation page for a specific data type in ZIO Blocks. Use when the user asks to document a data type, write an API reference for a type, or create a reference page for a class/trait/object.
wikidata-search
Search for items and properties on Wikidata and retrieve entity details, claims, and external identifiers. Supports both keyword search (Wikidata Action API) and semantic/hybrid search (Wikidata Vector Database), plus direct entity retrieval (Special:EntityData) and structured querying (WDQS SPARQL).
twelve-data-automation
Automate Twelve Data tasks via Rube MCP (Composio). Always search tools first for current schemas.
supadata-automation
Automate Supadata tasks via Rube MCP (Composio). Always search tools first for current schemas.
session-log-data
Describes the data files available in the coding agent environment after copilot-setup-steps runs. Use when analyzing downloaded session logs or aggregated usage data.
senior-data-scientist
World-class data science skill for statistical modeling, experimentation, causal inference, and advanced analytics. Expertise in Python (NumPy, Pandas, Scikit-learn), R, SQL, statistical methods, A/B testing, time series, and business intelligence. Includes experiment design, feature engineering, model evaluation, and stakeholder communication. Use when designing experiments, building predictive models, performing causal analysis, or driving data-driven decisions.
senior-data-engineer
World-class data engineering skill for building scalable data pipelines, ETL/ELT systems, and data infrastructure. Expertise in Python, SQL, Spark, Airflow, dbt, Kafka, and modern data stack. Includes data modeling, pipeline orchestration, data quality, and DataOps. Use when designing data architectures, building data pipelines, optimizing data workflows, or implementing data governance.
scientific-papers-to-dataset
Build structured datasets from academic papers. Use when the user wants to extract structured data from scientific literature, traverse citation graphs, search OpenAlex for papers, or create datasets from PDFs for research purposes.
repo-metadata
This skill should be used when the user asks to "update repo description", "improve repository description", "generate topics", "add labels to repo", "optimize github metadata", "make repo more discoverable", "improve repo SEO", "update project description", or needs to create engaging repository descriptions and topics that improve discoverability. Analyzes project files to generate optimized GitHub metadata.
peopledatalabs-automation
Automate Peopledatalabs tasks via Rube MCP (Composio). Always search tools first for current schemas.
parallel-data-enrichment
Structured company and entity data enrichment using Parallel AI Task API with core/base processors. Returns typed JSON output. No binary install — requires PARALLEL_API_KEY in .env.local.
pandas-data-manipulation-rules
Focuses on pandas-specific rules for data manipulation, including method chaining, data selection using loc/iloc, and groupby operations.