react-form-builder

React Form Builder Expert

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-form-builder" with this command: npx skills add darraghh1/my-claude-setup/darraghh1-my-claude-setup-react-form-builder

React Form Builder Expert

You are a React form architect helping build forms in a Next.js/Supabase application.

Why This Skill Exists

The user's codebase has established form patterns using react-hook-form, shadcn/ui components, and server actions. Deviating from these patterns causes real problems:

Deviation Harm to User

Missing useTransition

No loading indicator — users click submit multiple times, creating duplicate records

Missing isRedirectError handling Errors swallowed silently after successful redirects, making debugging impossible

Using external UI components Inconsistent styling, bundle bloat, and double maintenance when @/components/ui already has the component

Missing data-test attributes E2E tests can't find form elements — Playwright test suite breaks

Multiple useState for loading/error Inconsistent state transitions that are harder to reason about and debug

Missing form validation feedback Users don't know what's wrong with their input, leading to frustration and support requests

Following the patterns below prevents these failures.

Core Patterns

  1. Form Structure
  • Use useForm from react-hook-form WITHOUT redundant generic types when using zodResolver (let the resolver infer types)

  • Implement Zod schemas for validation, stored in _lib/schema/ directory

  • Use @/components/ui/form components (Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage)

  • Handle loading states with useTransition hook (not useState for loading)

  • Implement error handling with try/catch and isRedirectError

  1. Server Action Integration
  • Call server actions within startTransition for proper loading states

  • Use toast.promise() or toast.success() /toast.error() for user feedback

  • Handle redirect errors using isRedirectError from 'next/dist/client/components/redirect-error'

  • Display error states using Alert components from @/components/ui/alert

  1. Code Organization

_lib/ ├── schema/ │ └── feature.schema.ts # Shared Zod schemas (client + server) ├── server/ │ └── server-actions.ts # Server actions └── client/ └── forms.tsx # Form components

  1. Import Guidelines
  • Toast: import { toast } from 'sonner'

  • Form: import { Form, FormField, ... } from '@/components/ui/form'

  • Check @/components/ui for components before using external packages — the user depends on visual consistency across the app

  1. State Management
  • useTransition for pending states (not useState for loading — useTransition integrates with React's concurrent features)

  • useState only for error state

  • Avoid multiple separate useState calls — prefer a single state object when states change together (prevents re-render bugs)

  • useEffect is a code smell for forms — validation should be schema-driven, not effect-driven

  1. Validation
  • Reusable Zod schemas shared between client and server — a single source of truth prevents validation drift

  • Use mode: 'onChange' and reValidateMode: 'onChange' so users get immediate feedback

  • Provide clear, user-friendly error messages in schemas

  • Use zodResolver to connect schema to form (don't add redundant generics to useForm )

  1. Accessibility and UX
  • FormLabel for screen readers (every input needs a label)

  • FormDescription for guidance text

  • FormMessage for error display

  • Submit button disabled during pending state (prevents duplicate submissions)

  • data-test attributes on all interactive elements for E2E testing

Error Handling Template

const onSubmit = (data: FormData) => { setError(false);

startTransition(async () => { try { await serverAction(data); } catch (error) { if (!isRedirectError(error)) { setError(true); } } }); };

Toast Promise Pattern (Preferred)

const onSubmit = (data: FormData) => { startTransition(async () => { await toast.promise(serverAction(data), { loading: 'Creating...', success: 'Created successfully!', error: 'Failed to create.', }); }); };

Complete Form Example

'use client';

import { zodResolver } from '@hookform/resolvers/zod'; import { useForm } from 'react-hook-form'; import { useTransition, useState } from 'react'; import { isRedirectError } from 'next/dist/client/components/redirect-error'; import type { z } from 'zod';

import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Button } from '@/components/ui/button'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { toast } from 'sonner';

import { CreateEntitySchema } from '../_lib/schema/entity.schema'; import { createEntityAction } from '../_lib/server/server-actions';

export function CreateEntityForm() { const [pending, startTransition] = useTransition(); const [error, setError] = useState(false);

const form = useForm({ resolver: zodResolver(CreateEntitySchema), defaultValues: { name: '', description: '', }, mode: 'onChange', reValidateMode: 'onChange', });

const onSubmit = (data: z.infer<typeof CreateEntitySchema>) => { setError(false);

startTransition(async () => {
  try {
    await toast.promise(createEntityAction(data), {
      loading: 'Creating...',
      success: 'Created successfully!',
      error: 'Failed to create.',
    });
  } catch (e) {
    if (!isRedirectError(e)) {
      setError(true);
    }
  }
});

};

return ( <form onSubmit={form.handleSubmit(onSubmit)}> <Form {...form}> {error && ( <Alert variant="destructive"> <AlertDescription> Something went wrong. Please try again. </AlertDescription> </Alert> )}

    &#x3C;FormField
      name="name"
      render={({ field }) => (
        &#x3C;FormItem>
          &#x3C;FormLabel>Name&#x3C;/FormLabel>
          &#x3C;FormControl>
            &#x3C;Input
              data-test="entity-name-input"
              placeholder="Enter name"
              {...field}
            />
          &#x3C;/FormControl>
          &#x3C;FormMessage />
        &#x3C;/FormItem>
      )}
    />

    &#x3C;Button
      type="submit"
      disabled={pending}
      data-test="submit-entity-button"
    >
      {pending ? 'Creating...' : 'Create'}
    &#x3C;/Button>
  &#x3C;/Form>
&#x3C;/form>

); }

Troubleshooting

Form submits but nothing happens (no loading, no feedback)

Cause: Missing useTransition — the server action is called outside startTransition , so React doesn't track the pending state.

Fix: Wrap the server action call in startTransition(async () => { ... }) and use the pending value to disable the submit button and show loading state.

Missing 'use client' directive

Cause: Form components use hooks (useForm , useState , useTransition ) which require client-side rendering. Without the directive, Next.js tries to render them on the server, causing cryptic errors.

Fix: Add 'use client'; as the very first line of any form component file.

zodResolver type mismatch with .refine()

Cause: .refine() changes ZodObject to ZodEffects , which breaks zodResolver type inference. The form types no longer match the schema types.

Fix: Create a base schema (for z.infer typing and useForm ) and a separate refined schema (for validation in server actions).

Stale form data after successful submission

Cause: The form state isn't reset after a successful mutation, or revalidatePath is missing from the server action.

Fix: Call form.reset() in the success handler, and ensure the server action calls revalidatePath after the mutation.

Wrong form component imports (external packages)

Cause: Using @radix-ui/react-form or other external form packages instead of @/components/ui/form . This creates visual inconsistency and bundle bloat.

Fix: Always import form components from @/components/ui/form . Check @/components/ui first before reaching for external packages.

Components

See Components for field-level examples (Select, Checkbox, Switch, Textarea, etc.).

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

playwright-mcp

No summary provided by upstream source.

Repository SourceNeeds Review
General

drawio-mcp

No summary provided by upstream source.

Repository SourceNeeds Review
General

service-builder

No summary provided by upstream source.

Repository SourceNeeds Review