FormRenderer Frameworks Documentation
Overview
The FormRenderer component includes four composable frameworks that extend form capability with intelligent features. These frameworks are automatically enabled and work together seamlessly.
| Framework | Purpose | Status |
|---|---|---|
| Calculated Fields | Auto-compute values using formulas | ✅ Production Ready |
| Cascade Selects | Dependent select options | ✅ Production Ready |
| Async Validation | Real-time field validation | ✅ Production Ready |
| Repeater Discovery | Database-driven templates | ✅ Production Ready |
1. Calculated Fields Framework
Overview
Automatically computes field values based on mathematical and logical expressions. Updates in real-time as dependent fields change.
Built-in Functions
Math Functions:
SUM(field1, field2, ...)- Add valuesAVG(field1, field2, ...)- Calculate averageMAX(field1, field2)- Maximum valueMIN(field1, field2)- Minimum valueCEIL(number)- Round upFLOOR(number)- Round downROUND(number, decimals)- Round to decimalsABS(number)- Absolute valueMULT(field1, field2, ...)- Multiply values
Text Functions:
CONCAT(text1, text2, ...)- Concatenate stringsUPPER(text)- Uppercase conversionLOWER(text)- Lowercase conversionLEN(text)- String lengthTRIM(text)- Remove whitespaceSUBSTRING(text, start, length)- Extract substring
Logical Functions:
IF(condition, trueValue, falseValue)- Conditional expressionAND(cond1, cond2, ...)- Logical ANDOR(cond1, cond2, ...)- Logical ORNOT(condition)- Logical NOT
Date Functions:
TODAY()- Current dateNOW()- Current date and timeDATEDIFF(startDate, endDate, unit)- Days/months/years between dates
Configuration Example
// Repeater with calculated total
{
type: 'repeater',
name: 'orderItems',
children: [
{
type: 'number',
name: 'quantity',
label: 'Quantity'
},
{
type: 'number',
name: 'unitPrice',
label: 'Unit Price'
},
{
type: 'number',
name: 'lineTotal',
label: 'Line Total',
isCalculated: true,
formula: 'MULT(quantity, unitPrice)'
}
]
}
// Form-level calculated field
{
type: 'number',
name: 'grandTotal',
label: 'Grand Total',
isCalculated: true,
formula: 'SUM(orderItems[].lineTotal, tax, shipping)'
}
How to Use
- Add
isCalculated: trueto field configuration - Write formula using function syntax shown above
- Reference fields by name:
fieldNameor nested:parent.child - Array formulas: Use
fieldName[]notation for repeater sums - Framework handles: Real-time recalculation, dependency tracking, null handling
Advanced Examples
// Conditional calculation
{
type: 'number',
name: 'discount',
isCalculated: true,
formula: 'IF(totalAmount > 100, MULT(totalAmount, 0.1), 0)'
}
// Date-based calculation
{
type: 'number',
name: 'daysElapsed',
isCalculated: true,
formula: 'DATEDIFF(startDate, TODAY(), "days")'
}
// Nested property reference
{
type: 'text',
name: 'summary',
isCalculated: true,
formula: 'CONCAT(customer.firstName, " ", customer.lastName, " - ", orderStatus)'
}
Performance Notes
- Debounced: Recalculations batched to prevent excessive updates
- Dependency tracking: Only affected fields recalculated
- Error handling: Invalid formulas silently ignored, field shows as empty
- Scope: Calculations run after field change, before form submission
2. Cascade Selects Framework
Overview
Automatically populates select field options based on values in parent fields. Creates dependent dropdown hierarchies (country → state → city).
Configuration
// Parent field (required)
{
type: 'select',
name: 'country',
label: 'Country',
options: [
{ label: 'United States', value: 'US' },
{ label: 'Canada', value: 'CA' },
{ label: 'Mexico', value: 'MX' }
]
}
// Child field with cascade config
{
type: 'select',
name: 'state',
label: 'State/Province',
cascadeConfig: {
parentField: 'country',
// Option 1: Static options by parent value
optionsMap: {
'US': [
{ label: 'California', value: 'CA' },
{ label: 'Texas', value: 'TX' }
],
'CA': [
{ label: 'Ontario', value: 'ON' },
{ label: 'Quebec', value: 'QC' }
]
}
// Option 2: API endpoint with template
// apiUrl: '/api/states?country={country}'
}
}
// Grandchild field (multi-level cascade)
{
type: 'select',
name: 'city',
label: 'City',
cascadeConfig: {
parentField: 'state',
apiUrl: '/api/cities?state={state}' // Or use state value
}
}
API Template String
Template strings in apiUrl are automatically populated:
// Field value substitution
"/api/states?country={country}"; // Replaced with form data value
// Example substitution
// If form.country = 'US':
// Request: GET /api/states?country=US
How It Works
- User selects parent value (e.g., country = 'US')
- Framework detects parent field change
- Loads child options from static map or API
- Updates select with new options
- Clears child value if it's invalid in new option set
- Shows loading indicator while fetching
Response Format
API endpoint should return options array:
[
{ "label": "California", "value": "CA" },
{ "label": "Texas", "value": "TX" }
]
Or with grouped options:
{
"CT": [
{ "label": "New York", "value": "NY" },
{ "label": "Los Angeles", "value": "LA" }
],
"MX": [{ "label": "Mexico City", "value": "CDMX" }]
}
Multi-Level Cascading
Grandchild cascades from parent which cascades from root:
Country → updates → State → updates → City
Each level independently loads from API/static map.
Error Handling
- Invalid parent value: Child options become empty, child value cleared
- Parent cleared: Child field disabled until parent selected
- API error: Error logged, child options remain unchanged
- Timeout: Loading indicator shown for 5 seconds, then hidden
3. Async Validation Framework
Overview
Validates field values with optional asynchronous checks. Shows real-time validation errors and prevents form submission if validation fails.
Validation Types
// Required field
{ type: 'required', errorMessage: 'This field is required' }
// Email address
{ type: 'email', errorMessage: 'Please enter valid email' }
// URL
{ type: 'url', errorMessage: 'Please enter valid URL' }
// String length
{ type: 'minLength', value: 5, errorMessage: 'Minimum 5 characters' }
{ type: 'maxLength', value: 50, errorMessage: 'Maximum 50 characters' }
// Number range
{ type: 'min', value: 0, errorMessage: 'Must be at least 0' }
{ type: 'max', value: 100, errorMessage: 'Cannot exceed 100' }
// Regex pattern
{ type: 'pattern', pattern: '^[A-Z].*$', errorMessage: 'Must start with uppercase' }
// Async validation (API call)
{
type: 'async',
apiUrl: '/api/validate-email',
errorMessage: 'Email already registered',
debounce: 500
}
// Custom validator function
{
type: 'custom',
validator: (value, formData) => value !== formData.password ? null : 'Password must not match',
errorMessage: 'Validation failed'
}
Configuration Example
{
type: 'email',
name: 'email',
label: 'Email Address',
validation: [
{ type: 'required' },
{ type: 'email' },
{
type: 'async',
apiUrl: '/api/check-email-available',
errorMessage: 'Email already registered',
method: 'POST' // or 'GET'
}
]
}
Validation Timing
-
On field change (debounced 500ms):
- Sync validators run immediately
- Async validators queued after debounce
-
On form submission:
- All validators run regardless of debounce
- Form blocked if any validation fails
-
Display:
- Errors shown in red box below field
- "Validating..." indicator while async check in progress
- Success: Error container hidden
Async API Format
Request:
POST /api/validate-email
{
"value": "user@example.com",
"fieldName": "email",
"formData": { /* entire form data */ }
}
Response - Valid:
{ "valid": true }
Response - Invalid:
{
"valid": false,
"message": "Email already registered"
}
Error Display
Errors shown in formatted box:
- Location: Below form field
- Color: Red (#ef5350) background, dark red text
- Format: Bulleted list of errors
- Example:
❌ Email is required
❌ Email format is invalid
❌ Email already registered
Performance Optimization
- Debounced: 500ms delay before API call (prevent spam)
- Cached: Recent validation results cached to avoid duplicate requests
- Cancellable: In-flight requests cancelled if field changes again
- Timeout: 10 second timeout per async validator
4. Repeater Discovery Framework
Overview
Automatically discovers database table schemas and uses them as repeater item templates. Eliminates manual template configuration for database-backed data.
How It Works
- Configuration: Provide table name and connection ID
- On load: Framework queries database schema for table
- Template generation: Creates fields matching table columns
- Smart types: Assigns field types based on SQL column types
- Fallback: Manual template used if discovery unavailable
Configuration
{
type: 'repeater',
name: 'orderItems',
label: 'Order Items',
containerProps: {
discovery: {
tableName: 'OrderItems',
connectionId: 'primary_db', // Connection to query for schema
columns: ['productId', 'quantity', 'unitPrice'] // Optional: filter columns
}
}
}
Type Mapping
SQL Column Type → Field Type:
| SQL Type | Form Field | Input Type |
|---|---|---|
| NVARCHAR, VARCHAR | text | text |
| INT, BIGINT | number | number |
| DECIMAL, FLOAT | number | number |
| BIT | checkbox | checkbox |
| DATE, DATETIME | date | date |
| DATETIME2 | datetime | datetime |
| TIME | time | time |
Example Discovery Flow
// Table schema (SQL)
CREATE TABLE OrderItems (
id INT PRIMARY KEY,
productId NVARCHAR(50),
productName NVARCHAR(255),
quantity INT,
unitPrice DECIMAL(10,2),
discount DECIMAL(10,2),
inStock BIT,
createdDate DATETIME
)
// Discovered template (auto-generated):
// - productId → text field
// - productName → text field with maxLength (from nvarchar length)
// - quantity → number field
// - unitPrice → number field
// - discount → number field
// - inStock → checkbox
// - createdDate → date field
Fallback to Manual Template
If discovery fails, use manual configuration:
{
type: 'repeater',
name: 'items',
containerProps: {
itemTemplate: {
children: [
{ type: 'text', name: 'productId', label: 'Product ID' },
{ type: 'number', name: 'quantity', label: 'Quantity' }
]
}
}
}
Performance Considerations
- Cached: Schema cached in memory after first discovery
- Lazy: Discovery runs when form loads (not in real-time)
- Indexed: Table schemas indexed by connectionId + tableName
- Timeout: 5 second timeout per discovery request
Supported Database Types
- SQL Server (MSSQL)
- PostgreSQL
- MySQL
- SQLite
- Oracle
- MariaDB
- Azure SQL
- And 20+ others...
Framework Integration & Interactions
Execution Order
When form field changes:
- Cascade loads options (if cascadeConfig exists)
- Async validation runs (debounced 500ms)
- Calculated fields recalculate (only affected fields)
- onChange callback fires with updated data
Before Form Submission
- All frameworks validate (no debounce, immediate)
- Calculated fields finalize (get current values)
- Cascades finalized (lock in selected options)
- Validation errors shown (if any)
- Form submission blocked (if validation fails)
Disabling Frameworks
If needed, disable specific frameworks:
<FormRenderer
schema={schema}
data={data}
enableCalculatedFields={false} // Disable calculations
enableCascadeSelects={true} // Enable cascades
enableAsyncValidation={true} // Enable validation
enableRepeaterDiscovery={false} // Disable discovery
onSubmit={handleSubmit}
/>
Best Practices
✅ DO
- Use calculated fields for derived values (totals, percentages, dates)
- Use cascade selects for dependent geographic/hierarchical data
- Use async validation for uniqueness checks (email, username, SKU)
- Use repeater discovery for database tables (faster than manual)
- Handle errors gracefully - show helpful messages to users
- Test formulas with various data combinations
- Cache API responses in backend for cascade options
❌ DON'T
- Don't nest cascades more than 3 levels (performance impact)
- Don't use calculated fields for business logic (do in backend)
- Don't make async validators too strict (harder form completion)
- Don't forget API timeout handling (show fallback UI)
- Don't store sensitive data in calculated fields (no encryption)
- Don't rely on discovery for schema changes (cache stale data)
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Cascade options not loading | Parent field value missing or null | Ensure parent field has value before cascade runs |
| Calculated field shows empty | Formula syntax error | Check formula syntax, use valid function names |
| Validation errors not showing | Validator not configured correctly | Check validation array in field config |
| Discovery returns wrong fields | Table name incorrect or DB connection unavailable | Verify table name and connection ID in discovery config |
| Async validation never completes | API timeout or infinite loop | Check API endpoint, add timeout handler |
| Performance degradation | Too many calculated fields recalculating | Optimize formulas, reduce field dependencies |