@@ -208,10 +208,13 @@ const sizeOptions = [
208208 label="Birth Date"
209209 description="Select your date of birth"
210210 className="custom-class"
211+ disabled={isSubmitting} // Optional: disable during form submission
211212/>
213+ ```
212214
213- // Zod schema for date fields
214- const schema = z.object({
215+ **Zod Schema for Date Fields:**
216+ ```typescript
217+ const formSchema = z.object({
215218 birthDate: z.coerce.date({
216219 required_error: 'Please select a date',
217220 }),
@@ -224,14 +227,17 @@ const schema = z.object({
224227 name="fruit"
225228 label="Select Fruit"
226229 description="Choose your favorite fruit"
230+ disabled={isSubmitting} // Optional: disable during form submission
227231>
228232 <DropdownMenuSelectItem value="apple">Apple</DropdownMenuSelectItem>
229233 <DropdownMenuSelectItem value="banana">Banana</DropdownMenuSelectItem>
230234 <DropdownMenuSelectItem value="orange">Orange</DropdownMenuSelectItem>
231235</DropdownMenuSelect>
236+ ```
232237
233- // Zod schema for select fields
234- const schema = z.object({
238+ **Zod Schema for Select Fields:**
239+ ```typescript
240+ const formSchema = z.object({
235241 fruit: z.string({
236242 required_error: 'Please select a fruit',
237243 }),
@@ -246,10 +252,13 @@ const schema = z.object({
246252 description="Enter the 6-digit code sent to your phone"
247253 maxLength={6} // Required: number of digits
248254 className="custom-class"
255+ disabled={isSubmitting} // Optional: disable during form submission
249256/>
257+ ```
250258
251- // Zod schema for OTP
252- const schema = z.object({
259+ **Zod Schema for OTP Fields:**
260+ ```typescript
261+ const formSchema = z.object({
253262 otp: z.string().min(6, 'OTP must be 6 digits'),
254263});
255264```
@@ -302,7 +311,12 @@ const CustomMessage = (props: React.ComponentPropsWithoutRef<typeof FormMessage>
302311## Advanced Form Patterns
303312
304313### Custom Submit Handlers
305- Use `submitHandlers.onValid` to transform data before submission:
314+
315+ Use `submitHandlers.onValid` to transform data before submission. This is useful for:
316+ - Adding computed fields (timestamps, IDs)
317+ - Transforming data formats
318+ - Combining multiple fields
319+ - Adding metadata
306320
307321```typescript
308322const methods = useRemixForm<FormData>({
@@ -319,6 +333,7 @@ const methods = useRemixForm<FormData>({
319333 ...data,
320334 timestamp: new Date().toISOString(),
321335 processed: true,
336+ fullName: `${data.firstName} ${data.lastName}`, // Combine fields
322337 };
323338
324339 fetcher.submit(
@@ -334,20 +349,34 @@ const methods = useRemixForm<FormData>({
334349```
335350
336351### Checkbox Groups with Custom Submission
352+
353+ When working with multiple checkboxes, you often want to transform the boolean object into an array of selected values:
354+
355+ **Schema for Checkbox Group:**
337356```typescript
338- // Schema for checkbox group
339357const formSchema = z.object({
340358 colors: z.object({
341359 red: z.boolean().default(false),
342360 blue: z.boolean().default(false),
343361 green: z.boolean().default(false),
344362 }),
345363});
364+ ```
365+
366+ **Form Implementation:**
367+ ```typescript
368+ <div className="space-y-2">
369+ <Checkbox name="colors.red" label="Red" />
370+ <Checkbox name="colors.blue" label="Blue" />
371+ <Checkbox name="colors.green" label="Green" />
372+ </div>
373+ ```
346374
347- // Custom submission to transform checkbox data
375+ **Custom Submission Handler:**
376+ ```typescript
348377submitHandlers: {
349378 onValid: (data) => {
350- // Extract selected colors
379+ // Extract selected colors into array
351380 const selectedColors = Object.entries(data.colors)
352381 .filter(([_, selected]) => selected)
353382 .map(([color]) => color);
@@ -513,25 +542,50 @@ export const action = async ({ request }: ActionFunctionArgs) => {
513542## Success Handling & Redirects
514543
515544### Success Messages
545+
546+ Display success messages from server responses:
547+
516548```typescript
517549// In your component
518550{fetcher.data?.message && (
519551 <div className="mt-4 p-4 bg-green-50 border border-green-200 rounded-md">
520552 <p className="text-green-700 font-medium">{fetcher.data.message}</p>
521553 </div>
522554)}
555+
556+ // Error messages
557+ {fetcher.data?.errors?._form && (
558+ <div className="mt-4 p-4 bg-red-50 border border-red-200 rounded-md">
559+ <p className="text-red-700 font-medium">{fetcher.data.errors._form.message}</p>
560+ </div>
561+ )}
523562```
524563
525564### Programmatic Redirects
565+
566+ **Client-side redirect handling:**
526567```typescript
527- // In your component - handle redirect from server response
528- useEffect(() => {
529- if (fetcher.data?.redirectTo) {
530- navigate(fetcher.data.redirectTo);
531- }
532- }, [fetcher.data?.redirectTo, navigate]);
568+ import { useNavigate } from 'react-router';
569+
570+ const MyForm = () => {
571+ const navigate = useNavigate();
572+ const fetcher = useFetcher();
573+
574+ // Handle redirect from server response
575+ useEffect(() => {
576+ if (fetcher.data?.redirectTo) {
577+ navigate(fetcher.data.redirectTo);
578+ }
579+ }, [fetcher.data?.redirectTo, navigate]);
580+
581+ // ... rest of component
582+ };
583+ ```
584+
585+ **Server-side redirect:**
586+ ```typescript
587+ import { redirect } from 'react-router';
533588
534- // Or use Remix's redirect in the action
535589export const action = async ({ request }: ActionFunctionArgs) => {
536590 // ... validation and processing
537591
@@ -541,7 +595,12 @@ export const action = async ({ request }: ActionFunctionArgs) => {
541595```
542596
543597### Optimistic UI Updates
598+
599+ Show immediate feedback while form is submitting:
600+
544601```typescript
602+ import { useState, useEffect } from 'react';
603+
545604const MyForm = () => {
546605 const fetcher = useFetcher();
547606 const [optimisticMessage, setOptimisticMessage] = useState('');
@@ -876,29 +935,76 @@ export const action = async ({ request }: ActionFunctionArgs) => {
876935export default ComprehensiveForm;
877936```
878937
879- This example demonstrates:
880- - All major form components (TextField, Textarea, Checkbox, RadioGroup, DatePicker, DropdownMenuSelect)
881- - Complex validation with Zod schemas
882- - Conditional field display based on form values
883- - Custom submit handlers with data transformation
884- - Loading states and disabled inputs during submission
885- - Error handling for both client and server-side validation
886- - Success messages and redirect handling
887- - Responsive layout with proper spacing and styling
888- - Accessibility features built-in to all components
938+ ** This example demonstrates:**
939+ - ✅ All major form components (TextField, Textarea, Checkbox, RadioGroup, DatePicker, DropdownMenuSelect)
940+ - ✅ Complex validation with Zod schemas
941+ - ✅ Conditional field display based on form values
942+ - ✅ Custom submit handlers with data transformation
943+ - ✅ Loading states and disabled inputs during submission
944+ - ✅ Error handling for both client and server-side validation
945+ - ✅ Success messages and redirect handling
946+ - ✅ Responsive layout with proper spacing and styling
947+ - ✅ Accessibility features built-in to all components
889948
890- This comprehensive example should serve as a complete reference for implementing any form using the LambdaCurry Forms library!
949+ This comprehensive example serves as a complete reference for implementing any form using the LambdaCurry Forms library!
891950
892- ## Key Implementation Notes for LLMs
951+ ---
893952
894- 1. **Always use RemixFormProvider**: Wrap your form with `<RemixFormProvider {...methods}>` to provide form context
895- 2. **Form submission**: Use `fetcher.Form` with `onSubmit={methods.handleSubmit}` for proper form handling
896- 3. **Error handling**: Errors are automatically displayed via the `FormMessage` component when validation fails
897- 4. **Loading states**: Use `fetcher.state === 'submitting'` to show loading states and disable inputs
898- 5. **Custom components**: Override any part of the form structure using the `components` prop
899- 6. **Conditional rendering**: Use `methods.watch()` to conditionally show/hide fields based on form values
900- 7. **Data transformation**: Use `submitHandlers.onValid` to transform data before submission
901- 8. **Server validation**: Use `getValidatedFormData` in your action to validate and handle form data
902- 9. **Success handling**: Display success messages and handle redirects from the server response
903- 10. **Accessibility**: All components include proper ARIA attributes and accessibility features by default
953+ ## Key Implementation Notes for LLMs
904954
955+ ### Essential Patterns to Follow:
956+
957+ 1. **Form Provider Setup**
958+ ```typescript
959+ <RemixFormProvider {...methods}>
960+ <fetcher.Form onSubmit={methods.handleSubmit}>
961+ {/* Form components */}
962+ </fetcher.Form>
963+ </RemixFormProvider>
964+ ```
965+
966+ 2. **Error Handling**
967+ - Errors display automatically via `FormMessage` component
968+ - Server errors: `return { errors }` from action
969+ - Field-specific: `errors.fieldName.message`
970+ - Form-level: `errors._form.message`
971+
972+ 3. **Loading States**
973+ ```typescript
974+ const isSubmitting = fetcher.state === 'submitting';
975+ <TextField disabled={isSubmitting} />
976+ <Button disabled={isSubmitting}>
977+ {isSubmitting ? 'Submitting...' : 'Submit'}
978+ </Button>
979+ ```
980+
981+ 4. **Conditional Fields**
982+ ```typescript
983+ const watchValue = methods.watch('fieldName');
984+ {watchValue === 'condition' && <TextField name="conditionalField" />}
985+ ```
986+
987+ 5. **Data Transformation**
988+ ```typescript
989+ submitHandlers: {
990+ onValid: (data) => {
991+ const transformed = { ...data, computed: 'value' };
992+ fetcher.submit(createFormData(transformed), { method: 'post' });
993+ }
994+ }
995+ ```
996+
997+ 6. **Server Validation**
998+ ```typescript
999+ export const action = async ({ request }) => {
1000+ const { data, errors } = await getValidatedFormData(request, zodResolver(schema));
1001+ if (errors) return { errors };
1002+ // Process data...
1003+ };
1004+ ```
1005+
1006+ ### Important Reminders:
1007+ - 🔥 **Always import from `@lambdacurry/forms/remix-hook-form/`** for form-aware components
1008+ - 🔥 **Use `createFormData()` for custom submissions** to ensure proper formatting
1009+ - 🔥 **All components are accessible by default** - no additional ARIA setup needed
1010+ - 🔥 **Form context is automatic** - no need to pass `control` props manually
0 commit comments