import { pageMetadata } from "@/lib/page-metadata" export const metadata = pageMetadata("docs/validation") # Validation Validate form inputs with built-in and custom functions. ## Built-in Validators json-render includes common validation functions: - `required` — Value must be non-empty - `email` — Valid email format - `minLength` — Minimum string length (args: `{ "min": N }`) - `maxLength` — Maximum string length (args: `{ "max": N }`) - `pattern` — Match a regex pattern (args: `{ "pattern": "regex" }`) - `min` — Minimum numeric value (args: `{ "min": N }`) - `max` — Maximum numeric value (args: `{ "max": N }`) - `numeric` — Value must be a number - `url` — Valid URL format - `matches` — Must equal another field (args: `{ "other": { "$state": "/path" } }`) - `equalTo` — Alias for matches (args: `{ "other": { "$state": "/path" } }`) - `lessThan` — Value must be less than another field (args: `{ "other": { "$state": "/path" } }`) - `greaterThan` — Value must be greater than another field (args: `{ "other": { "$state": "/path" } }`) - `requiredIf` — Required only when another field is truthy (args: `{ "field": { "$state": "/path" } }`) ## Using Validation in JSON Use `{ "$bindState": "/path" }` on the value prop for two-way binding. Validation checks run against the value at the bound path (available as `bindings?.value` in components): ```json { "type": "TextField", "props": { "label": "Email", "value": { "$bindState": "/form/email" }, "checks": [ { "type": "required", "message": "Email is required" }, { "type": "email", "message": "Invalid email format" } ], "validateOn": "blur" } } ``` ## Validation with Parameters ```json { "type": "TextField", "props": { "label": "Password", "value": { "$bindState": "/form/password" }, "checks": [ { "type": "required", "message": "Password is required" }, { "type": "minLength", "args": { "min": 8 }, "message": "Password must be at least 8 characters" }, { "type": "pattern", "args": { "pattern": "[A-Z]" }, "message": "Must contain at least one uppercase letter" } ] } } ``` ## Custom Validation Functions Define custom validators in your catalog's `functions` field. The catalog itself is framework-agnostic — only the `schema` import varies by platform: ```typescript import { defineCatalog } from '@json-render/core'; import { schema } from '@json-render/react/schema'; // or '@json-render/react-native/schema' import { z } from 'zod'; const catalog = defineCatalog(schema, { components: { /* ... */ }, functions: { isValidPhone: { description: 'Validates phone number format', }, isUniqueEmail: { description: 'Checks if email is not already registered', }, }, }); ``` ## Usage with React In `@json-render/react`, use `ValidationProvider` to supply implementations for your custom validators: ```tsx import { ValidationProvider } from '@json-render/react'; function App() { const customValidators = { isValidPhone: (value) => { const phoneRegex = /^\+?[1-9]\d{1,14}$/; return phoneRegex.test(value); }, isUniqueEmail: async (value) => { const response = await fetch(`/api/check-email?email=${value}`); const { available } = await response.json(); return available; }, }; return ( {/* Your UI */} ); } ``` ### Using in Components The `useFieldValidation` and `useBoundProp` hooks wire validation into your registry components. Validation uses the path from `bindings?.value` (the bound state path): ```tsx import { useFieldValidation, useBoundProp } from '@json-render/react'; function TextField({ props, bindings }) { const [value, setValue] = useBoundProp(props.value, bindings?.value); const { errors, isValid, validate, touch, clear } = useFieldValidation( bindings?.value ?? null, { checks: props.checks, validateOn: props.validateOn } ); return (
setValue(e.target.value)} onBlur={() => validate()} /> {errors.map((error, i) => (

{error}

))}
); } ``` See the [@json-render/react API reference](/docs/api/react) for full `ValidationProvider` and `useFieldValidation` documentation. ## Cross-Field Validation Validation args support `{ "$state": "/path" }` references to compare against other fields. This enables cross-field rules like "confirm password must match password": ```json { "type": "Input", "props": { "label": "Confirm Password", "value": { "$bindState": "/form/confirmPassword" }, "checks": [ { "type": "required", "message": "Please confirm your password" }, { "type": "matches", "args": { "other": { "$state": "/form/password" } }, "message": "Passwords must match" } ] } } ``` Other cross-field examples: ```json { "checks": [ { "type": "greaterThan", "args": { "other": { "$state": "/form/startDate" } }, "message": "End date must be after start date" } ] } ``` ```json { "checks": [ { "type": "requiredIf", "args": { "field": { "$state": "/form/enableNotifications" } }, "message": "Email is required when notifications are enabled" } ] } ``` ## Conditional Validation Use the `enabled` field in the validation config to only run checks when a condition is met: ```json { "type": "Input", "props": { "label": "Company Name", "value": { "$bindState": "/form/company" }, "checks": [ { "type": "required", "message": "Company name is required" } ] } } ``` In the component implementation, you can pass `enabled` to `useFieldValidation`: ```typescript useFieldValidation(bindings?.value ?? "", { checks: props.checks ?? [], enabled: { "$state": "/form/accountType", eq: "business" }, }); ``` This only validates the company name when the account type is "business". ## Validation Timing Control when validation runs with `validateOn`: - `change` — Validate on every input change - `blur` — Validate when field loses focus (default for Input, Textarea) - `submit` — Validate only on form submission ## Form-Level Validation Use the built-in `validateForm` action to validate all registered fields at once. This is useful for a "Submit" button that should validate the entire form before proceeding: ```json { "type": "Button", "props": { "label": "Submit" }, "on": { "press": [ { "action": "validateForm", "params": { "statePath": "/formResult" } }, { "action": "submitForm" } ] }, "children": [] } ``` The `validateForm` action runs `validateAll()` and writes `{ valid: boolean }` to the specified state path (defaults to `/formValidation`). Your submit handler can then check `{ "$state": "/formResult/valid" }` to decide whether to proceed. > **Note:** Actions in a list execute sequentially, but `submitForm` does not automatically gate on validation. Guard submission with a `$cond` visibility condition on the button or check `{ "$state": "/formResult/valid" }` inside your action handler to skip submission when the form is invalid. ## Next - [Computed Values](/docs/computed-values) — derive dynamic prop values - [Watchers](/docs/watchers) — react to state changes - [Generation Modes](/docs/generation-modes) — how AI generates specs