use-form

Manage forms state, handles values and validation
Import

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, Checkbox, 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))}>
<TextInput
required
label="Email"
placeholder="your@email.com"
{...form.getInputProps('email')}
/>
<Checkbox
mt="md"
label="I agree to sell my privacy to this corporation"
{...form.getInputProps('termsOfService', { type: 'checkbox' })}
/>
<Button type="submit">Submit</Button>
</form>
);
}

API

use-form hook accepts configuration object as single argument:

  • initialValues – object with initial form values
  • validationRules – objects of functions that will be used to validate form values
  • errorMessages – object of react nodes that will be used to ser errors, if not provided errors will be set to true

Hook returns object with properties:

  • values – current form values
  • setValues – React useState hook setState action to set values
  • setFieldValue – function to set value of single field
  • validate – function to validate all values with validationRules
  • validateField – function to validate single field value with validationRules
  • errors – object of booleans which contains results of running validationRules functions on corresponding fields
  • setFieldError – function to set single field error in errors
  • resetErrors – function to set all errors to null
  • setErrors – React useState hook setState action to set errors
  • reset – function to reset all values and errors to initial state
  • onSubmit – wrapper function for form onSubmit event handler
  • getInputProps – returns an object with value, onChange and error that should be spread on input, work perfect with Mantine inputs

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 validation
const 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 field
const form = useForm({
initialValues: {
name: '',
age: 0, // age field is always valid
},
validationRules: {
// imposter validation rule is noop and will be ignored, ts will complain
imposter: (value) => value.trim().length >= 2,
name: (value) => value.trim().length >= 2,
},
});
const signUpForm = useForm({
initialValues: {
name: '',
password: '',
confirmPassword: '',
},
validationRules: {
name: (value) => value.trim().length >= 2,
password: (password) => password.trim().length >= 8,
// you can also get values as second argument in validation rule
confirmPassword: (confirmPassword, values) => confirmPassword === values.password,
},
});

errorMessages

You can provide errorMessages object that will be set to errors when validation fails:

const form = useForm({
initialValues: { name: '', age: 0 },
validationRules: {
name: (value) => value.trim().length >= 2,
age: (value) => value > 21,
},
errorMessages: {
name: 'Name must include at least 2 characters',
age: 'You must be 21 or older to enter',
},
});
form.validate();
form.errors;
// -> { name: 'Name must include at least 2 characters', age: 'You must be 21 or older to enter' }
form.setFieldValue('name', 'John');
form.validate();
form.errors;
// -> { name: null, age: 'You must be 21 or older to enter' }

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.getInputProps

getInputProps function returns an object with value (of checked for checkboxes), onChange and error that should be spread on input, work perfect with Mantine inputs:

const form = useForm({
initialValues: { name: 'initial-name', termsOfService: false },
validationRules: { name: (value) => value.trim().length >= 2 },
errorMessages: { name: 'Name must include at least 2 letters' },
});
form.getInputProps('name'); // -> returns object with the following properties:
// value – input value
// onChange – input onChange function, works with all Mantine and regular inputs
// error – error message if input is invalid
form.getInputProps('termsOfService', { type: 'checkbox' }); // -> returns object with the following properties:
// checked – input[type="checkbox"] checked value
// onChange – input onChange event, works with Mantine Checkbox, Switch and regular input[type="checkbox"]
// error – error message if input is invalid
// Use getInputProps to simplify common validation workflows:
import { TextInput, Checkbox } from '@mantine/core';
<TextInput label="Name" {...form.getInputProps('name')} />
<Checkbox label="Name" {...form.getInputProps('termsOfService', { type: 'checkbox' })} />

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 = (
<input
value={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 object
form.setValues({ name: 'Bob', age: 25 });
form.values; // -> { name: 'Bob', age: 25 }
// setValues with callback
form.setValues((currentValues) => ({ ...currentValues, age: currentValues.age + 10 }));
form.values; // -> { name: 'Bob', age: 35 }

form.validate

validate function runs all validation rules on corresponding value's key, it returns true if all fields are valid:

const form = useForm({
initialValues: { name: '', age: 0 },
validationRules: {
name: (value) => value.trim().length >= 2,
},
});
form.errors; // -> { name: null, age: null }
form.validate(); // -> false
form.errors; // -> { name: true, age: null }
form.setFieldValue('name', 'Bob');
form.validate(); // -> true
form.errors; // -> { name: null, age: null }

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: null }
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: null, age: null }
// Mark name field as invalid manually
form.setFieldError('name', true);
form.errors; // -> { name: true, age: null }
// Invalidate name field manually
form.setFieldError('name', null);
form.errors; // -> { name: null, age: null }
// Set custom error message
form.setFieldError('name', 'Error from server');
form.errors; // -> { name: 'Error from server', age: null }

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: null, age: null }
form.setErrors({ name: true, age: true });
form.errors; // -> { name: true, age: true }

form.resetErrors

resetErrors sets all errors to null:

const form = useForm({ initialValues: { name: '', age: 0 } });
form.errors; // -> { name: null, age: null }
form.setErrors({ name: true, age: true });
form.errors; // -> { name: true, age: true }
form.resetErrors();
form.errors; // -> { name: null, age: null }

form.reset

reset function sets all errors to null 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: null, age: null }
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 errors
const 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) },
errorMessages: { email: 'Invalid email' },
});
return (
<TextInput
required
label="Email"
placeholder="your@email.com"
onBlur={() => form.validateField('email')}
{...form.getInputProps('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} />
<TextInput
required
label="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

Browse code on GitHub

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,
Anchor,
useMantineTheme,
} from '@mantine/core';
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 toggleFormType = () => {
setFormType((current) => (current === 'register' ? 'login' : 'register'));
setError(null);
};
const form = useForm({
initialValues: {
firstName: '',
lastName: '',
email: '',
password: '',
confirmPassword: '',
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),
confirmPassword: (val, values) => formType === 'login' || val === values.password,
},
errorMessages: {
email: 'Invalid email',
password: 'Password should contain 1 number, 1 letter and at least 6 characters',
confirmPassword: "Passwords don't match. Try again",
},
});
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 (
<Paper
padding={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' && (
<Group grow>
<TextInput
data-autofocus
required
placeholder="Your first name"
label="First name"
{...form.getInputProps('firstName')}
/>
<TextInput
required
placeholder="Your last name"
label="Last name"
{...form.getInputProps('lastName')}
/>
</Group>
)}
<TextInput
mt="md"
required
placeholder="Your email"
label="Email"
icon={<EnvelopeClosedIcon />}
{...form.getInputProps('email')}
/>
<PasswordInput
mt="md"
required
placeholder="Password"
label="Password"
icon={<LockClosedIcon />}
{...form.getInputProps('password')}
/>
{formType === 'register' && (
<PasswordInput
mt="md"
required
label="Confirm Password"
placeholder="Confirm password"
icon={<LockClosedIcon />}
{...form.getInputProps('confirmPassword')}
/>
)}
{formType === 'register' && (
<Checkbox
mt="xl"
label="I agree to sell my soul and privacy to this corporation"
{...form.getInputProps('termsOfService', { type: 'checkbox' })}
/>
)}
{error && (
<Text color="red" size="sm" mt="sm">
{error}
</Text>
)}
{!noSubmit && (
<Group position="apart" mt="xl">
<Anchor
component="button"
type="button"
color="gray"
onClick={toggleFormType}
size="sm"
>
{formType === 'register'
? 'Have an account? Login'
: "Don't have an account? Register"}
</Anchor>
<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], values?: T) => boolean;
};
errorMessages?: {
[P in keyof T]?: React.ReactNode;
};
}): {
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 }
};
Build fully functional accessible web applications faster than ever
Feedback
Your feedback is most valuable contribution to the project, please share how you use Mantine, what features are missing and what is done good
Leave feedback