Interactive Forms with React¶
This section focuses on API and patterns of usage that are applicable to using newforms to create interactive forms in a React component in the browser.
Other documentation sections use the API for providing user input when creating a Form instance, to demonstrate behaviour and features based on user input in a concise way.
However, this API is more typical of using newforms on the server or as a
standalone validator. When working with Forms in the client, user input is more
often taken one field at a time using the onChange
event.
Provide a containing <form>
¶
Warning
You must provide a <form>
to contain fields you’re rendering with
newforms.
At the time of documenting (version 0.10), you must provide a <form>
to
contain fields you’re rendering with newforms. It’s likely that you’ll want one
anyway to make use of its onSubmit
event for Final Form validation.
React’s virtual DOM doesn’t provide a means of obtaining values from form
inputs. To do this for you, newforms must reach into the real DOM when an
onChange
event fires on one of the inputs it rendered for you.
Forms currently make use of the real DOM’s form.elements
collection to
simplify retrieving values for fields which render as multiple inputs, such
as a MultipleChoiceField
which uses a CheckboxSelectMultiple
widget.
noValidate
¶
Some newforms Widgets render HTML5 input types which, by default, perform their own validation.
When you’re using newforms with React for interactive validation, this can give
your forms an inconsistent feeling as the native inputs have their own way of
presenting validation errors. Consider adding a noValidate
prop to your
<form>
to disable native validation if this starts to feel like an issue:
<form onSubmit={this.handleSubmit} noValidate>
If you’re creating an isomorphic application, you might want to conditionally
use noValidate
so there’s some validation while the page is still initially
loading, or for scenarios when JavaScript isn’t available on the client:
getInitialState() {
return {client: false}
},
componentDidMount() {
this.setState({client: true})
},
render() {
return <form onSubmit={this.handleSubmit} noValidate={!this.state.client}>
...
Creating Forms and FormSets¶
There are a number of client-specific options available when creating an instance of a Form to use in a React component. The same options apply when creating FormSets, as they use them to handle creation of Form instances for you.
Form state and onChange()
¶
While a Form is not itself a React component, it is stateful. Its data
,
errors()
and cleanedData
properties will be changed when the user makes
changes and their input is stored and validated.
In order to update display of its containing React component, a Form will call
a given onChange()
callback each time user input is taken or validation is
performed in response to a user changing input data.
Note
The details of setting up onChange
are handled for you when rendering a
Form by passing its constructor to a <RenderForm/>
component.
Typically, this function will just force its React component to update, for example:
getInitialState: function() {
return {
form: new ContactForm({onChange: this.onFormChange})
}
},
onFormChange: function() {
this.forceUpdate()
}
Passing an onChange
callback will also automatically configure interactive
validation of each form input as it’s updated by the user. See below for details
of what that entails and how to configure it.
Warning
Due to the way controlled components work in React, if you are using
Controlled user inputs and you do not pass an onChange()
callback, your form
inputs will be read-only! The development version of newforms will warn you
if this happens.
Interactive Form validation¶
To validate individual input fields as the user interacts with them, you can pass
a validation
argument when instantiating a Form or Field; passing a
validation
argument when instantiating a Form sets up interactive validation
for every Field in it.
Form 'auto'
validation¶
Important
When you pass an onChange
callback to a Form, its validation
mode is
automatically implied to be 'auto'
:
var form = new SignupForm({onChange: this.onFormChange})
When the validation mode is 'auto'
:
- Text fields are validated using the
onChange
andonBlur
events, with a debounce delay of 369ms applied toonChange
between the last change being made and validation being performed. - Other input fields are validated as soon as the user interacts with them.
Note
React normalises the onChange
event in text inputs to fire after every
character which is entered.
'auto'
example form¶
Let’s use a standard signup form as an example:
var SignupForm = forms.Form.extend({
email: forms.EmailField(),
password: forms.CharField({widget: forms.PasswordInput}),
confirm: forms.CharField({label: 'Confirm password', widget: forms.PasswordInput}),
terms: forms.BooleanField({
label: 'I have read and agree to the Terms and Conditions',
errorMessages: {required: 'You must accept the terms to continue'}
}),
clean: function() {
if (this.cleanedData.password && this.cleanedData.confirm &&
this.cleanedData.password != this.cleanedData.confirm) {
throw forms.ValidationError('Passwords do not match.')
}
}
})
Note that this Form defines a clean() function
for cross-field validation. In addition to validating the field which just changed,
user input will also trigger cross-field validation by calling clean()
. This
function must always be written defensively regardless of whether full or partial
validation is being run, as it can’t assume that any of the cleanedData
it
validates against will be present due to the possibility of missing or invalid
user input.
Field validation¶
Fields also accept a validation
argument – validation defined at the field
level overrides any configured at the Form level, so if you want to use interaction
validation only for certain fields, or to opt fields out when validation has been
configured at the Form level, use the validation
argument when defining those
fields.
validation
options¶
'manual'
¶
This is the default option, which disables interactive validation.
You’re only likely to need to use this if you’re opting specific fields out of form-wide interactive validation.
validation
object¶
Interactive validation can be specified as an object with the following properties:
on
The name of the default event to use to trigger validation on text input fields. This can be specified with or without an
'on'
prefix. If validation should be triggerd by multiple events, their names can be passed as a space-delimited string or a list of strings.For example, given
validation: {on: 'blur'}
, text input validation will be performed when the input loses focus after editing.onChangeDelay
- A delay, in milliseconds, to be used to debounce performing of
validation when using the
onChange
event, to give the user time to enter input without distracting them with error messages or other disply changes around the input while they’re still typing.
'auto'
¶
The behaviour of 'auto'
validation is documented above.
It’s equivalent to passing:
validation: {on: 'blur change', onChangeDelay: 369}
Any event name¶
If you pass any other string as the validation
argument, it will be assumed
to be an event name, so the following lines are equivalent:
validation: 'blur'
validation: {on: 'blur'}
Final Form validation¶
Whether or not you’ve given your Form an onChange
callback, Forms will still
automatically update their data
object with user input as the user interacts
with each input field. Even if all fields have been used and are valid, the user
still has to signal their intent to submit before any final validation can be
performed.
Validating final form submission is left in your hands, as newforms doesn’t know
(or care, sorry!) what you ultimately want to do with the cleanedData
it
creates for you.
This is typically implemented by hooking into a <form>
‘s onSubmit
event
and calling form.validate()
to validate the entire user input.
onSubmit: function(e) {
e.preventDefault()
var form = this.state.form
var isValid = form.validate()
if (isValid) {
this.props.processContactData(form.cleanedData)
}
}
Tip
Forms represent groups of related Fields and don’t necessarily have to model
the content of the entire <form>
.
Use as many as you like, but don’t forget to use prefixes
when necessary to avoid input field name
and id
clashes.
Controlled user inputs¶
By default, newforms generates uncontrolled React components for user inputs, which can provide initial values but require manual updating via the DOM should you wish to change the displayed values from code.
If you need to programatically update the values displayed in user inputs after their initial display, you will need to use controlled React components.
You can do this by passing a controlled
argument when constructing the Form
or individual Fields you wish to have control over:
var form = new SignupForm({controlled: true, onChange: this.onFormChange})
Controlled components created by newforms reflect the values held in
form.data
. It’s recommended that you call form.setData()
or
form.updateData()
to update form.data
, as they handle transitioning from
initial display of data to displaying user input and will also call
onChange()
for you, to trigger re-rendering of the containing React
component.
controlled
example Form¶
An example of reusing the same controlled Form to edit a bunch of different objects which have the same fields.
First, define a form:
var PersonForm = forms.Form.extend({
name: forms.CharField({maxLength: 100}),
age: forms.IntegerField({minValue: 0, maxValue: 115}),
bio: forms.CharField({widget: forms.Textarea})
})
When creating the form in our example React component, we’re passing
controlled: true
:
getInitialState: function() {
return {
form: new PersonForm({
controlled: true,
onChange: this.forceUpdate.bind(this)
}),
editing: null,
people: [/* ... */]
}
}
To update what’s displayed in the form, we have a handleEdit
function in our
React component which is calling form.reset()
to put the form back into its
initial state, with new initial data:
handleEdit: function(personIndex) {
this.state.form.reset(this.state.people[personIndex])
this.setState({editing: personIndex})
}
Rendering Forms¶
One of the benefits of using React is that display logic really is Just JavaScript. Reusable pieces can be extracted into functions, or React components, or a configurable object of some sort or... whatever your programmery heart desires.
Newforms gives you a rendering helper – called a BoundField
– for each
field, which has access to the Field, its Widget and its Form, which
collectively have access to all the metadata and user input data it needs to
render the field. It uses these to implement rendering helper methods, which are
available for you to use in your react components.
BoundFields, their most useful properties and examples of their use are covered in Customising Form display and the complete BoundField API is documented.