@formality-ui/react
v0.0.0
Published
React implementation of the Formality form framework. Build powerful, dynamic forms with conditional logic, field dependencies, and auto-save support.
Readme
@formality-ui/react
React implementation of the Formality form framework. Build powerful, dynamic forms with conditional logic, field dependencies, and auto-save support.
Installation
npm install @formality-ui/react react-hook-form
# or
pnpm add @formality-ui/react react-hook-form
# or
yarn add @formality-ui/react react-hook-formPeer Dependencies:
react>= 18.0.0react-dom>= 18.0.0react-hook-form>= 7.0.0
Quick Start
import { FormalityProvider, Form, Field } from '@formality-ui/react';
import type { InputConfig, FormFieldsConfig } from '@formality-ui/react';
// Define your input types
const inputs: Record<string, InputConfig> = {
textField: {
component: ({ value, onChange, label, error, ...props }) => (
<div>
<label>{label}</label>
<input value={value ?? ''} onChange={(e) => onChange(e.target.value)} {...props} />
{error && <span>{error}</span>}
</div>
),
defaultValue: '',
},
switch: {
component: ({ value, onChange, label }) => (
<label>
<input type="checkbox" checked={value ?? false} onChange={(e) => onChange(e.target.checked)} />
{label}
</label>
),
defaultValue: false,
},
};
// Define your form fields
const config: FormFieldsConfig = {
name: { type: 'textField', label: 'Full Name' },
email: { type: 'textField', label: 'Email Address' },
subscribed: { type: 'switch', label: 'Subscribe to newsletter' },
};
// Use in your app
function App() {
return (
<FormalityProvider inputs={inputs}>
<Form config={config} onSubmit={(values) => console.log(values)}>
{({ methods }) => (
<form onSubmit={methods.handleSubmit(console.log)}>
<Field name="name" />
<Field name="email" />
<Field name="subscribed" />
<button type="submit">Submit</button>
</form>
)}
</Form>
</FormalityProvider>
);
}Components
FormalityProvider
Global configuration provider. Wrap your app or form section.
<FormalityProvider
inputs={inputConfigs}
validators={validatorConfigs}
formatters={formatterConfigs}
parsers={parserConfigs}
errorMessages={errorMessageConfigs}
>
{children}
</FormalityProvider>Props:
| Prop | Type | Description |
|------|------|-------------|
| inputs | Record<string, InputConfig> | Input component configurations |
| validators | ValidatorsConfig | Custom validators |
| formatters | FormattersConfig | Custom formatters |
| parsers | ParsersConfig | Custom parsers |
| errorMessages | ErrorMessagesConfig | Custom error messages |
Form
Form container with React Hook Form integration.
<Form
config={fieldConfigs}
formConfig={formLevelConfig}
record={initialValues}
onSubmit={handleSubmit}
autoSave={false}
debounce={1000}
>
{({ methods, formState, unusedFields, resolvedTitle }) => (
// Render your form
)}
</Form>Props:
| Prop | Type | Description |
|------|------|-------------|
| config | FormFieldsConfig | Field configurations |
| formConfig | FormConfig | Form-level configuration |
| record | Record<string, any> | Initial values |
| onSubmit | (values) => void | Submit handler |
| autoSave | boolean | Enable auto-save |
| debounce | number | Debounce delay (ms) |
Render API:
| Property | Type | Description |
|----------|------|-------------|
| methods | UseFormReturn | React Hook Form methods |
| formState | FormState | Form state |
| unusedFields | string[] | Fields not yet rendered |
| resolvedTitle | string | Resolved form title |
Field
Individual field with automatic configuration resolution.
<Field
name="fieldName"
type="textField"
disabled={false}
hidden={false}
label="Custom Label"
shouldRegister={true}
>
{({ fieldState, renderedField, fieldProps, watchers }) => (
// Custom render
)}
</Field>Props:
| Prop | Type | Description |
|------|------|-------------|
| name | string | Field name (required) |
| type | string | Override input type |
| disabled | boolean | Override disabled state |
| hidden | boolean | Hide field |
| label | string | Override label |
| shouldRegister | boolean | Register as used field |
Render API:
| Property | Type | Description |
|----------|------|-------------|
| fieldState | FieldState | Field state |
| renderedField | ReactNode | Rendered input component |
| fieldProps | object | Resolved field props |
| watchers | object | Watched field values |
FieldGroup
Apply conditions to multiple fields.
const formConfig = {
groups: {
signedFields: {
conditions: [{ when: 'signed', is: true, disabled: false }],
},
},
};
<FieldGroup name="signedFields">
<Field name="creditApp" />
<Field name="inCarvin" />
</FieldGroup>Props:
| Prop | Type | Description |
|------|------|-------------|
| name | string | Group name (must match formConfig.groups key) |
| children | ReactNode | Child fields/content |
UnusedFields
Render fields from config not explicitly placed.
<Form config={config}>
<Field name="name" />
{/* Other fields from config rendered automatically */}
<UnusedFields />
</Form>Props:
| Prop | Type | Description |
|------|------|-------------|
| exclude | string[] | Field names to exclude |
Conditions
Add conditional logic to fields:
const config: FormFieldsConfig = {
signed: { type: 'switch' },
creditApp: {
type: 'switch',
conditions: [
{ when: 'signed', is: false, disabled: true },
{ when: 'signed', is: true, visible: true },
],
},
};Condition Properties:
| Property | Description |
|----------|-------------|
| when | Field name to watch |
| selectWhen | Expression to evaluate |
| is | Exact value to match |
| truthy | Truthy/falsy match |
| disabled | Set disabled state when matched |
| visible | Set visibility when matched |
| set | Value to set when matched |
| selectSet | Expression for value to set |
Condition Merging Logic
- disabled: OR logic (disabled if ANY group/field is disabled)
- visible: AND logic (visible only if ALL groups/fields are visible)
Dynamic Props (selectProps)
Evaluate props dynamically based on form state:
const config: FormFieldsConfig = {
client: { type: 'autocomplete' },
clientContact: {
type: 'autocomplete',
selectProps: {
queryParams: 'client.id',
disabled: '!client',
placeholder: 'client.name',
},
},
};Auto-Save
Enable automatic form submission on changes:
<Form
config={config}
autoSave
debounce={2000}
onSubmit={async (values) => {
await saveToServer(values);
}}
>
{/* Fields */}
</Form>Hooks
useFormContext
Access form state and methods from any child component:
import { useFormContext } from '@formality-ui/react';
function CustomComponent() {
const { config, methods, record, unusedFields, submitImmediate } = useFormContext();
// ...
}useConditions
Evaluate conditions manually:
import { useConditions } from '@formality-ui/react';
const { disabled, visible, setValue } = useConditions({
conditions: fieldConfig.conditions,
});usePropsEvaluation
Evaluate dynamic props:
import { usePropsEvaluation } from '@formality-ui/react';
const evaluatedProps = usePropsEvaluation(selectProps, watchedValues);useFormState
Subscribe to form state changes:
import { useFormState } from '@formality-ui/react';
const { methods, formState } = useFormState(options);useSubscriptions
Subscribe to field value changes:
import { useSubscriptions } from '@formality-ui/react';
const watchedValues = useSubscriptions(fieldNames);useInferredInputs
Infer input configurations:
import { useInferredInputs } from '@formality-ui/react';
const inputs = useInferredInputs(config);Contexts
ConfigContext
Global configuration context:
import { useConfigContext } from '@formality-ui/react';
const { inputs, validators, formatters, parsers, errorMessages } = useConfigContext();FormContext
Form-level context:
import { useFormContext } from '@formality-ui/react';
const { config, methods, record, formConfig, unusedFields } = useFormContext();GroupContext
Group-level context for nested conditions:
import { useGroupContext } from '@formality-ui/react';
const groupState = useGroupContext();TypeScript Support
All types are exported for full TypeScript support:
import type {
// Components
FormalityProviderProps,
FormProps,
FormRenderAPI,
FieldProps,
FieldRenderAPI,
FieldGroupProps,
UnusedFieldsProps,
// Contexts
ConfigContextValue,
FormContextValue,
GroupContextValue,
GroupState,
// Core types (re-exported)
InputConfig,
FieldConfig,
FormFieldsConfig,
FormConfig,
ConditionDescriptor,
ValidationResult,
ValidatorSpec,
// React-specific types
InputTemplateProps,
CustomFieldState,
ExtendedFormState,
UseFormStateOptions,
WatcherSetterFn,
DebouncedFunction,
} from '@formality-ui/react';Utilities
makeProxyState
Create proxy state for efficient subscriptions:
import { makeProxyState, makeDeepProxyState } from '@formality-ui/react';
const proxy = makeProxyState(initialState);
const deepProxy = makeDeepProxyState(initialState);License
MIT
