Form Builder
Imports
import { FormBuilder, MultiStepFormBuilder } from '@codella-software/utils'
import { useFormBuilder } from '@codella-software/react'
import * as yup from 'yup'
FormBuilder
FormBuilder is created with FormBuilder.create<T>() or FormBuilder.fromConfig().
type LoginForm = {
email: string
password: string
}
const builder = FormBuilder.create<LoginForm>()
.addFields([
{ name: 'email', type: 'email', label: 'Email' },
{ name: 'password', type: 'password', label: 'Password' },
])
.setInitialValues({ email: '', password: '' })
.setValidationSchema(
yup.object({
email: yup.string().email().required(),
password: yup.string().min(8).required(),
}),
)
.onSubmit(async (values) => {
await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(values),
})
})
Useful builder methods:
addField(field)addFields(fields)addFieldGroup(prefix, group)setInitialValues(values)setValidationSchema(schema)setLayout(layout)setButtons(buttons)onSubmit(handler)onReset(handler)onChange(handler)setValidationBehavior({ validateOnChange, validateOnBlur, validateOnMount })use(middleware)
Runtime methods:
setFieldValue(name, value)setFieldTouched(name, touched?)validate()validateField(name, value?)submit()reset(values?)fillWithFakeData()— dev-only, see below
Async-select and async-validation methods are also part of the public API, including:
loadFieldOptions(fieldName, searchTerm?, force?)validateFieldAsync(name, value?)cancelAsyncValidation(name)
Dev tools
fillWithFakeData() fills every field with a plausible placeholder value, useful for skipping manual data entry while building a form. It's disabled by default and must be opted into via FormBuilderOptions:
const builder = FormBuilder.create<LoginForm>({
devTools: { fakeData: true },
}).addFields([
{ name: 'email', type: 'email', label: 'Email' },
{ name: 'password', type: 'password', label: 'Password' },
])
builder.fillWithFakeData()
Calling it without the flag enabled logs a warning and does nothing. Values are generated per field type (e.g. a random option for select/radio, a number within min/max, true for checkbox/switch); asyncSelect, file, custom, and dateRange fields are skipped since there's no generic value to fabricate for them. Only enable this flag in development.
Field types
Supported type values:
text, email, password, number, textarea, select, asyncSelect, combobox, checkbox, switch, radio, date, dateRange, time, file, array, custom
React hook
export function LoginFormView() {
const form = useFormBuilder({ builder })
return (
<form
onSubmit={(event) => {
event.preventDefault()
void form.submit()
}}
>
<input
value={form.values.email}
onChange={(event) => void form.setFieldValue('email', event.target.value)}
onBlur={() => form.setFieldTouched('email', true)}
/>
<input
value={form.values.password}
onChange={(event) => void form.setFieldValue('password', event.target.value)}
onBlur={() => form.setFieldTouched('password', true)}
/>
<button type="submit" disabled={form.isSubmitting}>
Sign in
</button>
</form>
)
}
useFormBuilder({ builder }) returns:
valueserrorstouchedcurrentSteptotalStepsisSubmittingisValidsetFieldValuesetFieldTouchedvalidatesubmitresetnextStepprevStep
nextStep and prevStep are placeholders on the plain hook. Actual multi-step orchestration lives in MultiStepFormBuilder.
MultiStepFormBuilder
type SignupForm = {
email: string
password: string
firstName: string
lastName: string
}
const wizard = MultiStepFormBuilder.create<SignupForm>()
.addSteps([
{
id: 'account',
title: 'Account',
fields: [
{ name: 'email', type: 'email', label: 'Email' },
{ name: 'password', type: 'password', label: 'Password' },
],
},
{
id: 'profile',
title: 'Profile',
fields: [
{ name: 'firstName', type: 'text', label: 'First name' },
{ name: 'lastName', type: 'text', label: 'Last name' },
],
},
])
.setInitialValues({
email: '',
password: '',
firstName: '',
lastName: '',
})
Public multi-step methods:
nextStep()previousStep()goToStep(stepIdOrIndex)validateCurrentStep()validateStep(stepId)validateAllSteps()submit()reset(values?)