Forms
Forms components
Most of the time you'll be using either AutoForm
or ValidatedForm
, but there are also other form components (rather low-level ones) with different capabilities.
Component | Self-generated? | Self-managed? | Self-validated? |
---|---|---|---|
AutoForm | ✔️ | ✔️ | ✔️ |
ValidatedQuickForm | ✔️ | ✖️ | ✔️ |
ValidatedForm | ✖️ | ✖️ | ✔️ |
QuickForm | ✔️ | ✖️ | ✖️ |
BaseForm | ✖️ | ✖️ | ✖️ |
AutoForm
AutoForm
extends ValidatedQuickForm
with state management.
It is the most user-friendly and commonly used form.
It's self-generated so if you provide a schema, the fields will be automatically rendered.
These fields will be also validated.
By default, the validation will take place onSubmit
, and onChange
after the first submission.
Props:
Name | Description |
---|---|
onChangeModel | Like onChange but for the whole model. Triggered just after onChange with the next model and information what { key, value, previousValue } caused the change. previousValue will be undefined if there was no value before. |
Note: All ValidatedQuickForm
props are also accepted and all methods are available.
In other words, that means that AutoForm
receives all props listed on this page.
Props usage:
import { AutoForm } from 'uniforms'; // Or from the theme package.
<AutoForm onChangeModel={model => console.log(model)} />;
ValidatedQuickForm
This form combines both QuickForm
and ValidatedForm
features.
It is not self-managed, however, it will automatically generate fields based on the provided schema and validate them.
Note: All QuickForm
props are also accepted and all methods are available.
Note: All ValidatedForm
props are also accepted and all methods are available.
ValidatedForm
(It's rather an internal form, but it's still exported.)
It's based on BaseForm
and extends its functionality by enabling automatic form validation.
Its purpose is providing validation functions.
It's not autogenerated, so if you want to see any fields rendered, you have to manually add them.
ValidatedForm
is not self-managed, so you won't be able to type anything until there is no onChange
handler,
however, there will be validation checks.
Props:
Name | Description |
---|---|
onValidate | Additional asynchronous validation. Schema validation has to be sync, so this is the only way to achieve async validation. |
validate | Validation mode. By default, the form will start to validate from the time of the first submit and then revalidate on every change. It's onChangeAfterSubmit . There are also onChange and onSubmit modes, but those are quite self-explanatory. |
validator | Validator options. It's passed to getValidator of your schema bridge. It really depends on your schema. |
Note: All BaseForm
props are also accepted and all methods are available.
Props usage:
import { ValidatedForm } from 'uniforms'; // Or from the theme package.
import { useRef } from 'react';
const formRef = useRef();
const formAction = () => {
// Reset form.
// It will reset changed state, model state in AutoForm, validation
// state in ValidatedForm and rerender.
formRef.reset();
// Trigger form change.
// It's a programmatic equivalent of a change event.
formRef.change(key, value);
// Submit form.
// It's a programmatic equivalent of a submit event. Returns a promise,
// which will either resolve with submitted form or reject with
// validation error in ValidatedForm.
formRef.submit();
};
<ValidatedForm
onValidate={async (model, error) => {
// You can either ignore validation error...
if (omitValidation(model)) {
return null;
}
// ...or any additional validation if an error is already there...
if (isSomeSpecialCase(error)) {
return MyAPI.checkOtherCondition(model);
}
// ...or feed it with another error.
return MyAPI.validate(model);
}}
validate="onChangeAfterSubmit"
validator={{ clean: true }}
ref={formRef}
/>;
QuickForm
(It's rather an internal form, but it's still exported.)
It's based on BaseForm
and extends its functionality by enabling automatic form generation.
If you provide a schema, the fields will be automatically rendered.
However, QuickForm
is not self-managed, so you won't be able to type anything until there is no onChange
handler.
You can customize which AutoField
should be used with AutoField.componentDetectorContext
.
Props:
Name | Description |
---|---|
errorsField | Custom ErrorsField . It should be anything that will pass through React.createElement . |
submitField | Custom SubmitField . It should be anything that will pass through React.createElement . |
Note: All BaseForm
props are also accepted and all methods are available.
Props usage:
import { QuickForm } from 'uniforms'; // Or from the theme package.
<QuickForm errorsField={CustomErrorsField} submitField={CustomSubmitField} />;
BaseForm
(It's rather an internal form, but it's still exported.)
It's the very basic form & foundation for the other forms.
It's not autogenerated, so if you want to see any fields rendered, you have to manually add them.
However, BaseForm
is not self-managed, so you won't be able to type anything until there is no onChange
handler.
Props:
Name | Description |
---|---|
autosaveDelay | Autosave delay. Set 0 for an instant autosave. |
autosave | Enable autosave. Every change triggers onSubmit . |
disabled | Default disabled prop for all fields. By default it's false - set it to true to disable the whole form. |
error | Validation error. Current validation state. It should be either compatible with your schema or an Error object. |
grid | Bootstrap grid layout style. Passing a number is an equivalent of {sm: n} . Object is a {mode: size} object. Complete string is simply passed through. Available in: bootstrap3, bootstrap4. |
model | Form model. An object with {field: value} structure. It doesn't matter if it has a prototype or not, but keep in mind that in onSubmit or in onChangeModel you'll receive a plain object. If you treat form as an input, then this is a value. |
modelTransform | Model transform. Function transforming one model into another. It's used in a few situations (modes) described below. Do not mutate a given model! |
onChange | Field change action. It receives two arguments: key and value, where the key is a dot-separated path to the changed field and value is a requested value. |
onSubmit | Submit action. When the form is submitted manually or by an HTML5 event, then it's called with the current model. Note: use Promise to return values and errors - synchronous return and throw are disallowed. |
readOnly | Default readOnly prop for all fields. By default it's false - set it to true to make the whole form read-only. |
schema | Form schema. It's used for form generation in QuickForm and validation in ValidatedForm. |
showInlineError | Default showInlineError prop for all fields. By default it's false - set it to true to enable inline errors for the whole form. Available in: antd, bootstrap3, bootstrap4, semantic. |
Props usage:
import { BaseForm } from 'uniforms'; // Or from the theme package.
import { useRef } from 'react';
const formRef = useRef();
const formAction = () => {
// Reset form.
// It will reset changed state, model state in AutoForm, validation
// state in ValidatedForm and rerender.
formRef.reset();
// Trigger form change.
// It's a programmatic equivalent of a change event.
formRef.change(key, value);
// Submit form.
// It's a programmatic equivalent of a submit event. Returns a promise,
// which will either resolve with submitted form or reject with
// validation error in ValidatedForm.
formRef.submit();
};
<BaseForm
autosaveDelay={0}
autosave={false}
disabled={false}
error={new Error('Nope.')}
grid={3} // 'col-3-sm' on label, 'col-9-sm' on input
grid="4" // 'col-4-sm' on label, 'col-8-sm' on input
grid={{ md: 5 }} // 'col-5-md' on label, 'col-7-md' on input
grid="col-6-xl" // 'col-6-xl' on label, 'col-6-xl' on input
model={{ fieldA: 1 }}
modelTransform={(mode, model) => {
// This model will be passed to the fields.
if (mode === 'form') {
/* ... */
}
// This model will be submitted.
if (mode === 'submit') {
/* ... */
}
// This model will be validated.
if (mode === 'validate') {
/* ... */
}
// Otherwise, return unaltered model.
return model;
}}
onChange={(key, value) => console.log(key, value)}
onSubmit={model => db.saveThatReturnsPromiseOrNothing(model)}
readOnly={false}
schema={myFormSchema}
showInlineError
ref={formRef}
/>;
Form features
Asynchronous validation
AutoForm
and ValidatedForm
both accept an onValidate
prop. It can be used to create an asynchronous validation:
The onValidate
should return null
if the model
is valid, otherwise return any error value. The error can be either Promise
for asynchronous validation or any other value for synchronous validation (https://github.com/vazco/uniforms/blob/d557f90e6807e34c1ebb9803d44fd799174175f8/packages/uniforms/src/ValidatedForm.tsx#L118-L142).
const MyAPI = {
checkOtherCondition(model): Error | null {
if (model.age < 18) {
return new Error('Too young')
}
return null
}
async validate(model): Error | null {
const result = await fetch('...', { body: JSON.stringify(model) })
const { error } = await result.json()
return error
}
}
const onValidate = async (model, error) => {
// You can either ignore validation error...
if (omitValidation(model)) {
return null;
}
// ...or any additional validation if an error is already there...
if (isSomeSpecialCase(error)) {
return MyAPI.checkOtherCondition(model);
}
// ...or feed it with another error.
return MyAPI.validate(model);
};
// Later...
<ValidatedForm {...props} onValidate={onValidate} />;
Autosave
Every form has autosave functionality. If you set an autosave
prop, then every change will trigger a submit. There's also an autosaveDelay
prop - a minimum time between saves in milliseconds (default: 0
).
Example:
<AutoForm
autosave
autosaveDelay={5000} // 5 seconds
schema={schema}
onSubmit={onSubmit}
/>
Methods
You can use React ref
prop to manually access form methods. Example usage:
import { useRef } from 'react';
const MyForm = ({ schema, onSubmit }) => {
const formRef = useRef();
return (
<section>
<AutoForm ref={formRef} schema={schema} onSubmit={onSubmit} />
<small onClick={() => formRef.reset()}>Reset</small>
<small onClick={() => formRef.submit()}>Submit</small>
</section>
);
};
You can do the same by using the useForm
hook and the formRef
property.
function FormControls() {
const { formRef } = useForm();
return (
<>
<button onClick={() => formRef.reset()}>Reset</button>
<button onClick={() => formRef.submit()}>Submit</button>
</>
);
}
function App() {
return (
<AutoForm>
<FormControls />
</AutoForm>
);
}
All available methods:
change(key, value)
Triggers a form change. It's a programmatic equivalent of a change event.
reset()
Resets a form. It will also reset changed state, model state (only in AutoForm), validation state (only in ValidatedForm) and trigger a rerender.
submit()
Submits a form. It's a programmatic equivalent of a submit event. Returns a promise, which will either resolve with a submitted model or reject with validation error in ValidatedForm.
validate()
(added in ValidatedForm
)
Validates a form with the current model. Returns a Promise, which rejects with a validation error or resolves without any value. Note, that it resolves/rejects after the component is rerendered.
validate(key, value)
(added in ValidatedForm
)
Validates a form with key set to value. You can use it to check, if a given value will pass the validation or not. Returns validation Promise, as described above.
validateModel(model)
(added in ValidatedForm
)
Validates a form with the given model. Returns validation Promise, as described above.
Change reactions
If you want to make one field to influence others, simply extend AutoForm
and override onChange
method.
Example:
class ChainForm extends AutoForm {
onChange(key, value) {
if (key === 'key_to_intercept') return;
if (key === 'key_to_translate') return super.onChange('another_key', value);
if (key === 'key_to_mutate') {
super.onChange('another_key1', value * 2);
super.onChange('another_key2', value / 2);
return;
}
super.onChange(key, value);
}
}
It can be easily applied multiple times to make your forms even more reusable.
Example:
const withMultipliedField = (fieldA, fieldB, Form) =>
class withMultipliedFieldForm extends Form {
onChange(key, value) {
// Multiply fieldA
if (key === fieldA) super.onChange(fieldB, value + value);
// Pass every change
super.onChange(key, value);
}
};
Model transformations
If you need to transform model before it will be validated, submitted or passed down to the fields, there's a modelTransform
prop, which should be used in those use cases.
Example:
<AutoForm
// Do not mutate given model!
modelTransform={(mode, model) => {
// This model will be passed to the fields.
if (mode === 'form') {
/* ... */
}
// This model will be submitted.
if (mode === 'submit') {
/* ... */
}
// This model will be validated.
if (mode === 'validate') {
/* ... */
}
// Otherwise, return unaltered model.
return model;
}}
onSubmit={onSubmit}
schema={schema}
/>
Validation options and modes
Any form can be validated in one those three styles:
onChange
Validate on every change.onChangeAfterSubmit
(default) Validate on every change, but only after first submit.onSubmit
Validate on every submit.
If your schema validator accepts any options, those can be passed in validator
prop.
Example:
<AutoForm
validate="onChange"
validator={validatorOptions}
schema={schema}
onSubmit={onSubmit}
/>