form-dev
guidelines for creating forms with logic in separate hooks, server actions, and useMutation.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
guidelines for creating forms with logic in separate hooks, server actions, and useMutation.
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Draft and post a Connexity release announcement to Discord after cutting a CLI or platform release. Use this skill whenever the user types `/announce-release`, or asks to "announce the release", "post the release to Discord", "draft a release announcement", "tell Discord about the new version", "share the new release", or anything that sounds like wanting to publicise a freshly cut release of `connexity-cli` or the platform. Trigger even when the user just says something casual like "ok the release is out, let's tell people" — that's the intended use case. Do not trigger for non-release announcements (blog posts, hiring, generic Discord messages).
TanStack Query streaming with Next.js App Router - server prefetching, dehydration, Suspense boundaries, useSuspenseQuery/useSuspenseInfiniteQuery hydration pattern. Use when implementing data fetching that streams from server to client using TanStack React Query with Next.js RSC.
Use this skill whenever the user types /commit or asks to "commit changes", "make a commit", "commit my work", or similar. Reads the current git branch state, diffs against the target branch (pulling latest first), drafts a well-structured conventional commit message, confirms with the user before pushing. Never adds "Co-Authored-by" lines. Always follow this skill for any commit workflow — do not improvise a different approach.
Use this skill whenever the user types /create-pr, /pr, or asks to "open a PR", "create a pull request", "make a PR", "submit for review", or similar. If there are uncommitted changes, runs the /commit skill first. Then crafts a well-structured, detailed PR title and body, confirms with the user, and only then opens the PR via gh CLI. Never adds "Co-Authored-by" or any AI attribution. Always follow this skill for any PR creation workflow.
Use this skill whenever the user types /review-task-implementation or asks to "review the implementation", "check if the task was implemented correctly", "review PR changes against the Jira task", "does this PR implement the ticket", or similar. This skill fetches Jira task details and PR context (comments, conversations, review threads), then performs a structured review of the local branch changes against the requirements. Use this skill any time someone wants to validate that code changes satisfy a Jira ticket, even if they phrase it casually like "does this look right for CS-42" or "check my work against the ticket".
| name | form-dev |
| description | guidelines for creating forms with logic in separate hooks, server actions, and useMutation. |
| disable-model-invocation | true |
This skill describes the project's standard pattern for developing forms. It ensures a clean separation of concerns between UI and business logic.
useMutation from TanStack Query (TSQ) for form submissions and data updates.mutationFn to useMutation.zod and react-hook-form's zodResolver) and the server (within the Server Action).The hook handles the form state, validation schema, and the mutation logic using TanStack Query (TSQ).
// useMyForm.ts
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { z } from 'zod';
import { myServerAction } from '@/lib/server-actions/my-actions';
import { toast } from '@/components/ui/sonner';
const formSchema = z.object({
name: z.string().min(1, 'name is required'),
});
type FormValues = z.infer<typeof formSchema>;
export const useMyForm = () => {
const queryClient = useQueryClient();
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: { name: '' },
});
const { mutateAsync, isPending } = useMutation({
mutationFn: myServerAction,
onSuccess: () => {
toast.success('saved successfully');
form.reset();
// queryClient.invalidateQueries({ queryKey: ['my-data'] });
},
onError: (error: Error) => {
toast.error('failed to save');
},
});
const onSubmit = form.handleSubmit(async (values) => {
await mutateAsync(values);
});
return { form, onSubmit, isPending };
};
The component receives props (if needed) and uses the hook to get the form state and handlers.
// MyForm.tsx
'use client';
import { useMyForm } from './useMyForm';
import {
Form,
FormField,
FormItem,
FormControl,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
export function MyForm() {
const { form, onSubmit, isPending } = useMyForm();
return (
<Form {...form}>
<form onSubmit={onSubmit} className="space-y-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormControl>
<Input {...field} placeholder="Enter name" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</Button>
</form>
</Form>
);
}
Mutation functions (Server Actions) must be organized within a server/[module]/actions folder. These actions act as entry points that call the business logic, often referred to as the "executor". Validation using Zod must occur again within the executor to ensure data integrity.
1. Define the Schema
server/issues/schemas/index.ts
import { z } from 'zod';
export const updateIssueSchema = z.object({
issueId: z.string().uuid(),
title: z.string().min(1),
summary: z.string().optional(),
});
2. Create the Server Action
server/issues/actions/index.ts
'use server';
import { updateIssue } from '@/server/issues/update-issue';
import { UpdateIssueType } from '@/server/issues/types';
export const updateIssueAction = async (args: UpdateIssueType) => {
return updateIssue(args);
};
3. Implement the Executor (Business Logic)
server/issues/update-issue.ts
The executor is wrapped with withAuth for security and withValidation to perform the Zod validation again on the server.
import { withValidation } from '@/app/_common/_validation/with-validation';
import { updateIssueSchema } from '@/server/issues/schemas';
import { withAuth } from '@/app/_common/_auth/with-auth';
import { ApiError, CustomError } from '@/server/errors';
import { prisma } from '@/lib/prisma/prisma';
export const updateIssue = withAuth(
withValidation(updateIssueSchema, async (args, user) => {
try {
const { issueId, title, summary } = args;
// Business logic and database operations
const updatedIssue = await prisma.issues.update({
where: { id: issueId },
data: {
title,
...(summary !== undefined && { summary }),
},
});
return updatedIssue;
} catch (error) {
if (error instanceof CustomError) {
throw error;
}
throw new ApiError('Internal server error');
}
}),
);
useMutation for side effects (POST/PUT/DELETE).toast at client\components\UI\toast.tsx for feedback.isPending is used to disable the submit button and show loading states.hooks subdirectory preferably.queryClient.invalidateQueries to refresh data after a successful mutation.