use-form
Usage
use-form provides bare minimum api to manage simple forms.
It includes handlers to set and validate values.
Hook does not depend on @mantine/core
inputs and does not work with dom.
import { TextInput, Button } from '@mantine/core';import { useForm } from '@mantine/hooks';export function Demo() {const form = useForm({initialValues: {email: '',termsOfService: false,},validationRules: {email: (value) => /^\S+@\S+$/.test(value),},});return (<form onSubmit={form.onSubmit((values) => console.log(values))}><TextInputrequiredlabel="Email"error={form.errors.email && 'Please specify valid email'}value={form.values.email}onChange={(event) => form.setFieldValue('email', event.currentTarget.value)}/><TextInputlabel="Name"value={form.values.name}onChange={(event) => form.setFieldValue('name', event.currentTarget.value)}/><Button type="submit">Submit</Button></form>);}
API
use-form hook accepts configuration
object as single argument:
initialValues
– object with initial form valuesvalidationRules
– objects of functions that will be used to validate form values
Hook returns object with properties:
values
– current form valuessetValues
– React useState hook setState action to setvalues
setFieldValue
– function to set value of single fieldvalidate
– function to validate allvalues
withvalidationRules
validateField
– function to validate single field value withvalidationRules
errors
– object of booleans which contains results of runningvalidationRules
functions on corresponding fieldssetFieldError
– function to set single field error inerrors
resetErrors
– function to set allerrors
to falsesetErrors
– React useState hook setState action to seterrors
reset
– function to reset allvalues
anderrors
to initial stateonSubmit
– wrapper function for form onSubmit event handler
initialValues
initialValues
is required for any form and defines form.values
shape.
When form is reset with form.reset
function these values are set to form.values
.
validationRules
validationRules
is an optional object of functions which are used to validate form.values
.
If you do not need validation in your form, you can skip them:
// Form without validationconst form = useForm({ initialValues: { name: '', age: 0 } });
validationRules
must include only keys from initialValues
,
keys from initialValues
that do not have corresponding validation rule will always be considered valid.
// validation will run only on name fieldconst form = useForm({initialValues: {name: '',age: 0, // age field is always valid},validationRules: {// imposter validation rule is noop and will be ignored, ts will complainimposter: (value) => value.trim().length >= 2,name: (value) => value.trim().length >= 2,},});
form.values
values
contains current values of form, it has the same shape as initialValues
:
const form = useForm({ initialValues: { name: '', age: 0 } });form.values; // -> { name: '', age: 0 }form.setFieldValue('name', 'Bob');form.values; // -> { name: 'Bob', age: 0 }
form.setFieldValue
setFieldValue
function sets value at given key on values
object:
const form = useForm({ initialValues: { name: '', age: 0 } });form.setFieldValue('name', 'Bob');form.setFieldValue('age', 25);form.values; // -> { name: 'Bob', age: 25 }
Usually this function is used to work with input elements:
const form = useForm({ initialValues: { name: '' } });const input = (<inputvalue={form.values.name}onChange={(event) => form.setFieldValue('name', event.currentTarget.value)}/>);
form.setValues
setValues
allows you to set all values
with single function call:
const form = useForm({ initialValues: { name: '', age: 0 } });// setValues with objectform.setValues({ name: 'Bob', age: 25 });form.values; // -> { name: 'Bob', age: 25 }// setValues with callbackform.setValues((currentValues) => ({ ...currentValues, age: currentValues.age + 10 }));form.values; // -> { name: 'Bob', age: 35 }
form.validate
validate
function runs all validation rules on corresponding values
key:
const form = useForm({initialValues: { name: '', age: 0 },validationRules: {name: (value) => value.trim().length >= 2,},});form.errors; // -> { name: false, age: false }form.validate();form.errors; // -> { name: true, age: false }form.setFieldValue('name', 'Bob');form.validate();form.errors; // -> { name: false, age: false }
form.validateField
validateField
function allows you to run validations for individual fields,
for example, it can be useful if you want to validate field when it loses focus:
const form = useForm({initialValues: { name: '', age: 0 },validationRules: {name: (value) => value.trim().length >= 2,age: (value) => value >= 18,},});form.setFieldValue('age', 12);form.validateField('age');form.errors; // -> { age: true, name: false }form.validateField('name');form.errors; // -> { age: true, name: true }
form.setFieldError
setFieldError
allows you to bypass validation and manipulate errors
object as you wish.
For example, you can remove error from field once it was focused or perform your own validation:
const form = useForm({initialValues: { name: '', age: 0 },validationRules: {name: (value) => value.trim().length >= 2,},});form.errors; // -> { name: false, age: false }form.setFieldError('name', true);form.errors; // -> { name: true, age: false }form.setFieldError('name', true);form.errors; // -> { name: false, age: false }
form.setErrors
setErrors
sets errors
object.
Use it when external fields validation occurs, e.g. on server:
const form = useForm({ initialValues: { name: '', age: 0 } });form.errors; // -> { name: false, age: false }// You can set errors with objectform.setErrors({ name: true, age: true });form.errors; // -> { name: true, age: true }// or with callbackform.setErrors((errors) => ({name: !errors.name,age: !errors.age,}));form.errors; // -> { name: false, age: false }
form.resetErrors
resetErrors
sets all errors
to false:
const form = useForm({ initialValues: { name: '', age: 0 } });form.errors; // -> { name: false, age: false }form.setErrors({ name: true, age: true });form.errors; // -> { name: true, age: true }form.resetErrors();form.errors; // -> { name: false, age: false }
form.reset
reset
function sets all errors
to false and sets values
to initialValues
:
const form = useForm({ initialValues: { name: '', age: 0 } });form.setErrors({ name: true, age: true });form.setValues({ name: 'Bob', age: 25 });form.errors; // -> { name: true, age: true }form.values; // -> { name: 'Bob', age: 25 }form.reset();form.errors; // -> { name: false, age: false }form.values; // -> { name: '', age: 0 }
form.onSubmit:
onSubmit
takes function as an argument and calls it with values if form has no validation errors:
const form = useForm({initialValues: { name: '', age: 0 },validationRules: {name: (value) => value.trim().length >= 2,},});// console.log will be called with form.values only if// form.validate does not encounter errorsconst authForm = <form onSubmit={form.onSubmit((values) => console.log(values))} />;
Examples
Validate field on blur
import { TextInput } from '@mantine/core';import { useForm } from '@mantine/hooks';export function Demo() {const form = useForm({initialValues: { email: '' },validationRules: {email: (value) => /^\S+@\S+$/.test(value),},});return (<TextInputrequiredlabel="Email"error={form.errors.email && 'Please specify valid email'}value={form.values.email}onChange={(event) => form.setFieldValue('email', event.currentTarget.value)}onBlur={() => form.validateField('email')}/>);}
External field validation
Submit form with test@mantine.dev
email to see external validation error:
import { useState } from 'react';import { TextInput, Button } from '@mantine/core';import { useForm } from '@mantine/hooks';export function Demo() {const [loading, setLoading] = useState(false);const [serverError, setServerError] = useState<string>(null);const form = useForm({initialValues: { email: 'test@mantine.dev' },validationRules: {email: (value) => /^\S+@\S+$/.test(value),},});const handleSubmit = (values: typeof form['values']) => {setLoading(true);setTimeout(() => {setLoading(false);if (values.email === 'test@mantine.dev') {setServerError('Email already taken');form.setFieldError('email', true);}}, 1500);};return (<form onSubmit={form.onSubmit(handleSubmit)} style={{ position: 'relative' }}><LoadingOverlay visible={loading} /><TextInputrequiredlabel="Email"placeholder="your@email.com"error={form.errors.email && (serverError || 'Please specify valid email')}value={form.values.email}onChange={(event) => form.setFieldValue('email', event.currentTarget.value)}onFocus={() => {setServerError(null);form.setFieldError('email', false);}}/><Button type="submit">Register</Button></form>);}
Authentication form
import React, { useState } from 'react';import { useForm } from '@mantine/hooks';import { EnvelopeClosedIcon, LockClosedIcon } from '@modulz/radix-icons';import {TextInput,PasswordInput,Group,Checkbox,Button,Paper,Text,LoadingOverlay,useMantineTheme,} from '../../src';export interface AuthenticationFormProps {noShadow?: boolean;noPadding?: boolean;noSubmit?: boolean;style?: React.CSSProperties;}export function AuthenticationForm({noShadow,noPadding,noSubmit,style,}: AuthenticationFormProps) {const [formType, setFormType] = useState<'register' | 'login'>('register');const [loading, setLoading] = useState(false);const [error, setError] = useState<string>(null);const theme = useMantineTheme();const inputVariant = theme.colorScheme === 'dark' ? 'filled' : 'default';const toggleFormType = () => {setFormType((current) => (current === 'register' ? 'login' : 'register'));setError(null);};const form = useForm({initialValues: {firstName: '',lastName: '',email: '',password: '',termsOfService: true,},validationRules: {firstName: (value) => formType === 'login' || value.trim().length >= 2,lastName: (value) => formType === 'login' || value.trim().length >= 2,email: (value) => /^\S+@\S+$/.test(value),password: (value) => /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,}$/.test(value),},});const handleSubmit = () => {setLoading(true);setError(null);setTimeout(() => {setLoading(false);setError(formType === 'register'? 'User with this email already exists': 'User with this email does not exist');}, 3000);};return (<Paperpadding={noPadding ? 0 : 'lg'}shadow={noShadow ? null : 'sm'}style={{position: 'relative',backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white,...style,}}><form onSubmit={form.onSubmit(handleSubmit)}><LoadingOverlay visible={loading} />{formType === 'register' && (<div style={{ display: 'flex', marginBottom: 15 }}><TextInputdata-autofocusrequiredplaceholder="Your first name"label="First name"style={{ marginRight: 20, flex: '0 0 calc(50% - 10px)' }}value={form.values.firstName}onChange={(event) => form.setFieldValue('firstName', event.currentTarget.value)}onFocus={() => form.setFieldError('firstName', false)}error={form.errors.firstName && 'First name should include at least 2 characters'}variant={inputVariant}/><TextInputrequiredplaceholder="Your last name"label="Last name"style={{ flex: '0 0 calc(50% - 10px)' }}value={form.values.lastName}onChange={(event) => form.setFieldValue('lastName', event.currentTarget.value)}onFocus={() => form.setFieldError('lastName', false)}error={form.errors.lastName && 'Last name should include at least 2 characters'}variant={inputVariant}/></div>)}<TextInputrequiredplaceholder="Your email"label="Email"icon={<EnvelopeClosedIcon />}value={form.values.email}onChange={(event) => form.setFieldValue('email', event.currentTarget.value)}onFocus={() => form.setFieldError('email', false)}error={form.errors.email && 'Field should contain a valid email'}variant={inputVariant}/><PasswordInputstyle={{ marginTop: 15 }}requiredplaceholder="Password"label="Password"showPasswordLabel="Show password"hidePasswordLabel="Hide password"icon={<LockClosedIcon />}value={form.values.password}onChange={(event) => form.setFieldValue('password', event.currentTarget.value)}onFocus={() => form.setFieldError('password', false)}variant={inputVariant}error={form.errors.password &&'Password should contain 1 number, 1 letter and at least 6 characters'}/>{formType === 'register' && (<Checkboxstyle={{ marginTop: 20 }}label="I agree to sell my soul and privacy to this corporation"checked={form.values.termsOfService}onChange={(event) => form.setFieldValue('termsOfService', event.currentTarget.checked)}/>)}{error && (<Text color="red" size="sm" style={{ marginTop: 10 }}>{error}</Text>)}{!noSubmit && (<Group position="apart" style={{ marginTop: 25 }}><Button variant="link" color="gray" onClick={toggleFormType} size="sm">{formType === 'register'? 'Have an account? Login': "Don't have an account? Register"}</Button><Button color="blue" type="submit">{formType === 'register' ? 'Register' : 'Login'}</Button></Group>)}</form></Paper>);}
TypeScript
Definition
function useForm<T extends { [key: string]: any }>(configuration: {initialValues: T;validationRules?: {[P in keyof T]?: (value: T[P]) => boolean;};}): {values: T;errors: Record<keyof T, boolean>;validate: () => boolean;reset: () => void;resetErrors: () => void;setValues: React.Dispatch<React.SetStateAction<T>>;setErrors: React.Dispatch<React.SetStateAction<Record<keyof T, boolean>>>;setFieldValue: <K extends keyof T, U extends T[K]>(field: K, value: U) => void;setFieldError: (field: keyof T, error: boolean) => void;validateField: (field: keyof T) => void;onSubmit: (handleSubmit: (values: T) => any) => (event?: React.FormEvent) => void;};
Set values type
use-form will use values types from initialValues, but you can pass your own type:
const form = useForm<{ name?: string; termsOfService?: boolean }>({initialValues: {name: '',termsOfService: false,},});
Get form values type
Use typeof
to get form values type:
const form = useForm({ initialValues: { email: '' } });const handleSubmit = (values: typeof form['values']) => {// values – { email: string }};