Skip to Content
FrontendBundlesCRUDForm Components

Form Components

The CRUD bundle provides powerful form components that dynamically render form fields based on configuration, handle validation, and support multi-language translations. These components integrate seamlessly with React Hook Form and Zod validation.


Core Architecture

Field Configuration System

All form components use a unified field configuration system:

interface FilterField { key: string; // Field identifier/name title?: string; // Display label type: ColumnTypeEnum; // Field type placeholder?: string; // Input placeholder targetResourceAsync?: string; // For async selects isMulti?: boolean; // Multiple selections optionsArrayFinite?: { // Static options label: string; value: string | boolean; }[]; required?: boolean; // Is required field resourceAsyncLabelParam?: string; // API label parameter prefixOnSubmit?: string; // Prefix for submission default?: boolean; // Default value range?: boolean; // Range input rangeKeys?: { // Range field keys min: string; max: string; }; translatable?: boolean; // Multi-language support itemSpecificPath?: string; // Item-specific path helperText?: string; // Help text }

Supported Field Types

enum ColumnTypeEnum { TEXT = "text", EMAIL = "email", NUMBER = "number", TEL = "tel", SELECT = "select", DATE = "date", PASSWORD = "password", SELECT_ASYNC = "select_async", URL = "url", CHECKBOX = "checkbox", TEXTAREA = "textarea", BOOLEAN = "boolean", ICON = "icon", COLOR = "color", CURRENCY = "currency", ENTITY_URL = "entityUrl", DECIMAL = "DECIMAL", FLOAT = "FLOAT", DATETIME = "DATETIME", TIME = "TIME", UNORDERED_LIST = "ul", ORDERED_LIST = "ol", }

Component Overview

1. RenderFormFields

Dynamic form field renderer that supports all field types with validation and internationalization.

Props

interface RenderFormFieldsProps { input: FilterField; // Field configuration fqcn_bui: IFQCN_BUI; // Component configuration form: any; // React Hook Form instance (TODO: INFER TYPE FOR FORM LATER) formValues?: Record<string, any>; // Pre-populated values isSubmitting?: boolean; // Form submission state showLabel?: boolean; // Show field labels locale?: string; // Locale for translations tenant?: string; // Tenant identifier csrfJwt?: string; // CSRF token for API calls }

Features

  • 30+ Field Types: Comprehensive field type support
  • Real-time Validation: Live validation with error display
  • Conditional Fields: Show/hide based on other field values
  • Dependent Fields: Fields that change options based on other selections
  • Accessibility: Full ARIA support and keyboard navigation
  • Internationalization: Multi-language support with translations
  • Custom Styling: Tailwind CSS with theme support

Usage Example

import { RenderFormFields } from "@phpcreation/frontend-crud-react-nextjs-bundle/components"; import { useForm } from "react-hook-form"; const formFields: FilterField[] = [ { key: "name", title: "Full Name", type: ColumnTypeEnum.TEXT, required: true, placeholder: "Enter your full name", validation: { minLength: 2, maxLength: 100, }, grid: { xs: 12, md: 6 }, }, { key: "email", title: "Email Address", type: ColumnTypeEnum.EMAIL, required: true, placeholder: "user@example.com", grid: { xs: 12, md: 6 }, }, { key: "age", title: "Age", type: ColumnTypeEnum.NUMBER, validation: { min: 18, max: 120, }, grid: { xs: 12, md: 4 }, }, { key: "country", title: "Country", type: ColumnTypeEnum.COUNTRY, required: true, grid: { xs: 12, md: 4 }, }, { key: "state", title: "State/Province", type: ColumnTypeEnum.STATE, dependsOn: "country", grid: { xs: 12, md: 4 }, }, { key: "bio", title: "Biography", type: ColumnTypeEnum.TEXTAREA, placeholder: "Tell us about yourself...", helperText: "Maximum 500 characters", validation: { maxLength: 500, }, grid: { xs: 12 }, }, { key: "newsletter", title: "Subscribe to Newsletter", type: ColumnTypeEnum.BOOLEAN, grid: { xs: 12 }, }, { key: "interests", title: "Interests", type: ColumnTypeEnum.SELECT_ASYNC, apiEndpoint: "/api/interests/search", isMulti: true, conditional: { field: "newsletter", value: true, }, grid: { xs: 12 }, }, ]; function UserRegistrationForm() { const form = useForm({ defaultValues: { name: "", email: "", age: null, country: "", state: "", bio: "", newsletter: false, interests: [], }, }); const onSubmit = (data: any) => { console.log("Form submitted:", data); }; return ( <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <div className="grid grid-cols-12 gap-4"> {formFields.map((field) => ( <div key={field.key} className={`col-span-12 ${ field.grid?.md ? `md:col-span-${field.grid.md}` : "" } ${field.grid?.lg ? `lg:col-span-${field.grid.lg}` : ""}`} > <RenderFormFields input={field} fqcn_bui={{ Bundle: "user", Unit: "registration", Interface: "form", }} form={form} showLabel={true} locale="en" tenant="company" csrfJwt={csrfToken} /> </div> ))} </div> <div className="flex justify-end gap-4"> <Button type="button" variant="outline"> Cancel </Button> <Button type="submit" disabled={form.formState.isSubmitting}> {form.formState.isSubmitting ? "Submitting..." : "Submit"} </Button> </div> </form> ); }

2. RenderFilterFields

Specialized component for rendering filter fields with range support and advanced filtering options.

Props

type RenderFilterFieldsProps = { filter: FilterField; // Filter configuration fqcn_bui: IFQCN_BUI; // Component configuration filters: Record<string, any>; // Current filter values handleFilterChange: ( // Filter change handler key: string, items: string | number | boolean | (string | number | boolean)[] ) => void; isSubmitting?: boolean; // Form submission state locale?: string; // Locale for translations tenant?: string; // Tenant identifier csrfJwt?: string; // CSRF token };

Features

  • Range Filters: Min/max value filtering for numbers and dates
  • Multi-select Filters: Multiple value selection
  • Async Search: Server-side filtering with search
  • Date Ranges: Start and end date filtering
  • Boolean Filters: True/false/all options

Usage Example

import { RenderFilterFields } from "@phpcreation/frontend-crud-react-nextjs-bundle/components"; const filterFields: FilterField[] = [ { key: "search", title: "Search", type: ColumnTypeEnum.TEXT, placeholder: "Search users...", }, { key: "status", title: "Status", type: ColumnTypeEnum.SELECT, isMulti: true, options: [ { label: "Active", value: "active" }, { label: "Inactive", value: "inactive" }, { label: "Pending", value: "pending" }, ], }, { key: "age_range", title: "Age Range", type: ColumnTypeEnum.NUMBER, range: true, }, { key: "created_date", title: "Registration Date", type: ColumnTypeEnum.DATE, range: true, }, { key: "department_id", title: "Department", type: ColumnTypeEnum.SELECT_ASYNC, apiEndpoint: "/api/departments/search", searchEndpoint: "/api/departments/search", }, ]; function UserFilters() { const [filters, setFilters] = useState({}); const handleFilterChange = (key: string, value: any) => { setFilters((prev) => ({ ...prev, [key]: value, })); }; return ( <div className="bg-white p-4 rounded-lg shadow"> <h3 className="text-lg font-semibold mb-4">Filters</h3> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> {filterFields.map((filter) => ( <RenderFilterFields key={filter.key} filter={filter} fqcn_bui={{ Bundle: "user", Unit: "listing", Interface: "filters" }} filters={filters} handleFilterChange={handleFilterChange} locale="en" tenant="company" csrfJwt={csrfToken} /> ))} </div> <div className="flex justify-end gap-2 mt-4"> <Button variant="outline" onClick={() => setFilters({})}> Clear Filters </Button> <Button onClick={() => applyFilters(filters)}>Apply Filters</Button> </div> </div> ); }

3. TranslatableFields

Component for managing multi-language form fields with tabbed interface.

Props

interface TranslatableFieldsProps { languages?: string[]; // Supported languages initialValues?: Record<string, Record<string, any>>; // Initial translations fqcn_bui?: IFQCN_BUI; // Component configuration defaultLanguage?: string; // Default language locale?: string; // Current locale }

Features

  • Multi-language Tabs: Separate tabs for each language
  • Field Validation: Per-language validation
  • Default Language: Fallback language support
  • Translation Status: Visual indicators for translated/untranslated fields
  • Synchronized Updates: Changes propagate across language tabs

Usage Example

import { TranslatableFields } from "@phpcreation/frontend-crud-react-nextjs-bundle/components"; import { FormProvider, useForm } from "react-hook-form"; function ProductForm() { const form = useForm({ defaultValues: { // Regular fields sku: "", price: 0, // Translatable fields apiTranslations: { en_CA: { title: "", description: "", }, fr_CA: { title: "", description: "", }, es: { title: "", description: "", }, }, }, }); return ( <FormProvider {...form}> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> {/* Regular fields */} <div className="grid grid-cols-2 gap-4"> <RenderFormFields input={{ key: "sku", title: "SKU", type: ColumnTypeEnum.TEXT, required: true, }} fqcn_bui={fqcn_bui} form={form} /> <RenderFormFields input={{ key: "price", title: "Price", type: ColumnTypeEnum.CURRENCY, required: true, }} fqcn_bui={fqcn_bui} form={form} /> </div> {/* Translatable fields */} <div className="border rounded-lg p-4"> <h3 className="text-lg font-semibold mb-4">Translations</h3> <TranslatableFields languages={["en", "fr", "es"]} fqcn_bui={{ Bundle: "product", Unit: "form", Interface: "translations", }} defaultLanguage="en" locale="en" /> </div> <Button type="submit">Save Product</Button> </form> </FormProvider> ); }

Advanced Field Types

Rich Text Editor

const richTextField: FilterField = { title: "Notes", key: "notes", type: ColumnTypeEnum.TEXTAREA, placeholder: "Input the Notes", required: false, };
const asyncSelectField: FilterField = { title: "Id", key: "id", placeholder: "Select Id", type: ColumnTypeEnum.SELECT_ASYNC, targetResourceAsync: "users", isMulti: true, default: true, };

Conditional Fields

const conditionalFields: FilterField[] = [ { key: "user_type", title: "User Type", type: ColumnTypeEnum.SELECT, options: [ { label: "Individual", value: "individual" }, { label: "Business", value: "business" }, ], }, { key: "company_name", title: "Company Name", type: ColumnTypeEnum.TEXT, required: true, conditional: { field: "user_type", value: "business", }, }, { key: "tax_id", title: "Tax ID", type: ColumnTypeEnum.TEXT, conditional: { field: "user_type", value: "business", }, }, ];

Validation Integration

Zod Schema Generation

The form components automatically generate Zod schemas from field configurations:

import { validationSchema } from "@phpcreation/frontend-crud-react-nextjs-bundle/utils/functions"; const fields: FilterField[] = [ { key: "email", title: "Email", type: ColumnTypeEnum.EMAIL, required: true, }, { key: "age", title: "Age", type: ColumnTypeEnum.NUMBER, }, ]; // Auto-generated schema const schema = validationSchema(fields); // Equivalent to: // z.object({ // email: z.string().email("Invalid email"), // age: z.number().min(18).max(120) // })

Best Practices

Performance - Use React.memo for expensive field renders - Implement

debouncing for async selects - Lazy load heavy components like WYSIWYG editors

  • Use field-level validation to avoid full form re-validation

Accessibility - Always provide meaningful labels and descriptions - Use

proper ARIA attributes - Ensure keyboard navigation works - Test with screen readers - Provide clear error messages


Complete Form Example

import React from 'react'; import { useForm, FormProvider } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { RenderFormFields, TranslatableFields } from "@phpcreation/frontend-crud-react-nextjs-bundle/components"; const userFields: FilterField[] = [ { key: "personal_info", title: "Personal Information", type: "section" // Section divider }, { key: "first_name", title: "First Name", type: ColumnTypeEnum.TEXT, required: true, grid: { xs: 12, md: 6 } }, { key: "last_name", title: "Last Name", type: ColumnTypeEnum.TEXT, required: true, grid: { xs: 12, md: 6 } }, { key: "email", title: "Email Address", type: ColumnTypeEnum.EMAIL, required: true, grid: { xs: 12, md: 6 } }, { key: "phone", title: "Phone Number", type: ColumnTypeEnum.TEL, grid: { xs: 12, md: 6 } }, { key: "address_info", title: "Address Information", type: "section" }, { key: "country", title: "Country", type: ColumnTypeEnum.COUNTRY, required: true, grid: { xs: 12, md: 4 } }, { key: "state", title: "State/Province", type: ColumnTypeEnum.STATE, dependsOn: "country", grid: { xs: 12, md: 4 } }, { key: "city", title: "City", type: ColumnTypeEnum.CITY, dependsOn: "state", grid: { xs: 12, md: 4 } }, { key: "preferences", title: "Preferences", type: "section" }, { key: "newsletter", title: "Subscribe to Newsletter", type: ColumnTypeEnum.BOOLEAN, grid: { xs: 12 } }, { key: "interests", title: "Areas of Interest", type: ColumnTypeEnum.SELECT_ASYNC, apiEndpoint: "/api/interests/search", isMulti: true, conditional: { field: "newsletter", value: true }, grid: { xs: 12 } } ]; function UserRegistrationForm() { const form = useForm({ resolver: zodResolver(validationSchema(userFields)), defaultValues: { first_name: "", last_name: "", email: "", phone: "", country: "", state: "", city: "", newsletter: false, interests: [] } }); const onSubmit = async (data: any) => { try { await createUser(data); toast.success("User registered successfully!"); } catch (error) { toast.error("Registration failed"); } }; return ( <FormProvider {...form}> <div className="max-w-4xl mx-auto p-6"> <div className="bg-white rounded-lg shadow-md p-6"> <h1 className="text-2xl font-bold mb-6">User Registration</h1> <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> <div className="grid grid-cols-12 gap-4"> {userFields.map((field) => { if (field.type === "section") { return ( <div key={field.key} className="col-span-12"> <h3 className="text-lg font-semibold border-b pb-2 mb-4"> {field.title} </h3> </div> ); } return ( <div key={field.key} className={`col-span-12 ${ field.grid?.md ? `md:col-span-${field.grid.md}` : '' }`} > <RenderFormFields input={field} fqcn_bui={{Bundle: "user", Unit: "registration", Interface: "form"}} form={form} showLabel={true} locale="en" tenant="company" csrfJwt={csrfToken} /> </div> ); })} </div> <div className="flex justify-end gap-4 pt-4 border-t"> <Button type="button" variant="outline"> Cancel </Button> <Button type="submit" disabled={form.formState.isSubmitting} > {form.formState.isSubmitting ? "Registering..." : "Register"} </Button> </div> </form> </div> </div> </FormProvider> ); }

This comprehensive documentation covers all form components with detailed configuration options, usage examples, and best practices for building dynamic, validated forms with multi-language support.

Last updated on