Skip to main content

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.

FrameworkPurposeStatus
Calculated FieldsAuto-compute values using formulas✅ Production Ready
Cascade SelectsDependent select options✅ Production Ready
Async ValidationReal-time field validation✅ Production Ready
Repeater DiscoveryDatabase-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 values
  • AVG(field1, field2, ...) - Calculate average
  • MAX(field1, field2) - Maximum value
  • MIN(field1, field2) - Minimum value
  • CEIL(number) - Round up
  • FLOOR(number) - Round down
  • ROUND(number, decimals) - Round to decimals
  • ABS(number) - Absolute value
  • MULT(field1, field2, ...) - Multiply values

Text Functions:

  • CONCAT(text1, text2, ...) - Concatenate strings
  • UPPER(text) - Uppercase conversion
  • LOWER(text) - Lowercase conversion
  • LEN(text) - String length
  • TRIM(text) - Remove whitespace
  • SUBSTRING(text, start, length) - Extract substring

Logical Functions:

  • IF(condition, trueValue, falseValue) - Conditional expression
  • AND(cond1, cond2, ...) - Logical AND
  • OR(cond1, cond2, ...) - Logical OR
  • NOT(condition) - Logical NOT

Date Functions:

  • TODAY() - Current date
  • NOW() - Current date and time
  • DATEDIFF(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

  1. Add isCalculated: true to field configuration
  2. Write formula using function syntax shown above
  3. Reference fields by name: fieldName or nested: parent.child
  4. Array formulas: Use fieldName[] notation for repeater sums
  5. 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

  1. User selects parent value (e.g., country = 'US')
  2. Framework detects parent field change
  3. Loads child options from static map or API
  4. Updates select with new options
  5. Clears child value if it's invalid in new option set
  6. 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

  1. On field change (debounced 500ms):

    • Sync validators run immediately
    • Async validators queued after debounce
  2. On form submission:

    • All validators run regardless of debounce
    • Form blocked if any validation fails
  3. 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

  1. Configuration: Provide table name and connection ID
  2. On load: Framework queries database schema for table
  3. Template generation: Creates fields matching table columns
  4. Smart types: Assigns field types based on SQL column types
  5. 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 TypeForm FieldInput Type
NVARCHAR, VARCHARtexttext
INT, BIGINTnumbernumber
DECIMAL, FLOATnumbernumber
BITcheckboxcheckbox
DATE, DATETIMEdatedate
DATETIME2datetimedatetime
TIMEtimetime

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:

  1. Cascade loads options (if cascadeConfig exists)
  2. Async validation runs (debounced 500ms)
  3. Calculated fields recalculate (only affected fields)
  4. onChange callback fires with updated data

Before Form Submission

  1. All frameworks validate (no debounce, immediate)
  2. Calculated fields finalize (get current values)
  3. Cascades finalized (lock in selected options)
  4. Validation errors shown (if any)
  5. 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

IssueCauseSolution
Cascade options not loadingParent field value missing or nullEnsure parent field has value before cascade runs
Calculated field shows emptyFormula syntax errorCheck formula syntax, use valid function names
Validation errors not showingValidator not configured correctlyCheck validation array in field config
Discovery returns wrong fieldsTable name incorrect or DB connection unavailableVerify table name and connection ID in discovery config
Async validation never completesAPI timeout or infinite loopCheck API endpoint, add timeout handler
Performance degradationToo many calculated fields recalculatingOptimize formulas, reduce field dependencies