zustand-forms
v1.1.2
Published
Form validator for Zustand
Maintainers
Readme
Zustand-based form validator
For any reasons You can write me letter to [email protected]
✨ Features
🌐 Locale Support
locale: Locale→ Current form language (default: browser locale)setLocale(locale: Locale)→ Change form language dynamically
✅ Validation & State
isValid(): boolean→ Checks if the entire form is validerrors: FormErrorsType<T>→ Maps fields to their first error (e.g.,errors.yourField)valid: FormValidFlagsType<T>→ Per-field validity flags (e.g.,valid.yourField)validate(silent?: boolean, include?: Array<keyof T>)→ Triggers validation (silent validation (without putting error toerrors) for fields with new values, or not silent - with putting errors)
🔄 Form Control
reset()→ Resets form toinitialValuessetValues(values: Partial<T>)→ Updates form values and silent validation (without putting error toerrors) for fields with new valuessetInitialValues(values: Partial<T>)→ Updates initial values (for dirty-checking)
📌 Form Binding
bind: FormBindType<T>→ Methods to bind inputs to formbind.yourField.value- Value getter (alias ofvalues.yourField)bind.yourField.onBlur- Callback to bind blur event of fieldyourField, also you can do it viaonBlur('yourField')bind.yourField.onChange(to: string)- Callback to send change of fieldyourFieldto form storage, also you can do it viaonChange({yourField: toValue})
onChange(fieldValue: Partial<T>)→ Handles value changes (e.g.:onChange={(to) => onChange({ yourField: to })})onBlur(field: keyof T)→ Handles blur events (useful for validation, e.g.:onBlur={() => onBlur('yourField')})
🔍 State Tracking
values: T→ Current form values map (e.g.,values.yourField)initialValues: T→ Original/default/initial values (e.g.,initialValues.yourField)hasModified(): boolean→ Checks ifvalues≠initialValues(dirty state)
Step by step sample
0. Imagine form
That contains 4 fields: name, email and password twice. Two password fields should be equal.
1. Define validators
const nameValidator = (v) => v && v.length > 3 || 'Name should have 3 or more letters'
const passwordsShouldBeEqualsValidator = (v, {values}) => v === values.password2 || 'Password should be equal'2. Define form
import { createFormValidator } from 'zustand-forms';
import { emailValidator } from 'zustand-forms/validators'
const useMyForm = createFormValidator<{
name: string
email: string
passowrd1: string
password2: string
}>({
name: {
required: true
},
email: {
required: true,
rules: [emailValidator],
},
password1: {
required: true,
},
password2: {
required: true,
rules: [passwordsShouldBeEqualsValidator],
},
})3. Use form validator
export const MyFormComponent: FC = () => {
const {
bind,
isValid,
errors,
} = useMyForm();
return <form>
{errors.name && <label>{errors.name}</label>}
<input placeholder="Name" {...bind.name}/>
{errors.email && <label>{errors.email}</label>}
<input placeholder="Email" {...bind.email}/>
{errors.password1 && <label>{errors.password1}</label>}
<input placeholder="Password" {...bind.password1}/>
{errors.password2 && <label>{errors.password2}</label>}
<input placeholder="Repeat password" {...bind.password2}/>
<input type="submit" disabled={!isValid()}/>
</form>
}More samples:
📌 Complex sample:
// Custom validators
const codeValidator = (v) => /^\d{6}$/.test(v) || 'Enter correct code'
// Define form
export const useMyNiceForm = createFormValidator<{
name: string
code: string
}>({
name: {
required: true,
},
code: {
required: true,
rules: [codeValidator],
initialValue: '000000',
},
})
const AnyComponent = () => {
const {
values,
errors,
onChange,
onBlur,
isValid,
bind,
reset,
hasModified,
setInitialValues,
setValues,
} = useSupportRequestForm();
// Change initial values (e.g. loaded from back)
setInitialValues({
name: 'Default name',
code: ''
})
// Mass set values (e.g. loaded from backend)
setValues({
name: 'Name from backend',
code: '123456'
})
const areSubmitDisabled = !isValid() || !hasModified()
return <>
<Input
label='Name'
// Via values
value={values.name}
// Via bindings
value={bind.name.value}
errorMessage={errors.name}
// Or via bind
onBlur={bind.name.onBlur}
// Or own
onBlur={() => onBlur('name')}
// Or via bind
onChange={bind.name.onChange}
// Or own
onChange={(name) => onChange({ name })}
// Or just pass props
{...bind.name}
/>
{/* Simple use when onChange emmits value (not ChangeEvent)*/}
<Input
label='Code'
errorMessage={errors.code}
{...bind.code}
/>
<Button type='submit' disabled={areSubmitDisabled} value='Send'/>
</>
}📌 Validation across multiple fields:
const passwordsShouldBeEqualsValidator = (v, {values}) => v === values.password2 || 'Password should be equal'
export const useMyNiceForm = createFormValidator<{
password1: string
password2: string
}>({
password1: {
required: true,
},
password2: {
required: true,
rules: [passwordsShouldBeEqualsValidator],
},
})📋 TO DO
[ ] Add predefined validators
- [ ] Validate string length
- [ ] Validate number - is greater or is lower
- [ ] Validate date - in range, greater, lower
- [ ] Password strength validation1
[ ] Tests coming soon!
[ ] Make docs more obviously
[ ] Nice obviously examples and use cases
[ ] Performance tests
[ ] Validation modes (on change, on blur, or combined)
[ ] I18n for custom validators (for cases, when app are multilingual)
[ ] Customizable binding interface (For passing
{...bind.yourField}to element props to completely bind it to the form) - reason: different UI KIT's have different props names, in one place -onChange, in other justchangeprop of<Input/>component.
