Forms
Forms handle state, validation, and error handling so that the internal components don't have to worry about them.
The Form
component works by building up its fields
, where the field name, initial value, label, and rendered children
are defined. These fields
can then be used within the Form
A Form
will generally look like this:
<Form {...{
fields: {
fieldName: {/*...*/}
}
}}>
{({fields}) => {
return (
<div>
{fields.fieldName}
</div>
);
}}
</Form>
Basic Forms
We can set an initialValue
to pre-fill each field. When the form is reset before submitting,
all fields will revert to their initialValue
, if provided. In the Email Form below, the email field has been pre-filled to my@me.com
.
Optional Fields
When you set optional: true
on a field, it can affect both the appearance of the field and the behavior
of the form. The text "(Optional)" is added to the field label, and the field is no longer considered required.
To change the text that is added to the label, set the optionalText
property within the field object. In the example below,
we have set optional: true
and optionalText: '(Opt)'
for the Last Name
field.
Inline Forms & Label Position
When you set the inline
property to true
, the label gets positioned next to the field instead
of above it. By default, the label will appear to the left of the field, but you can set labelPosition: 'after'
to place the label on the right.
Tooltips
The tooltip
prop makes an icon with a tooltip appear next to the label.
tooltipSize
can be set to: sm
, md
or lg
in order to control it's size. And it's placement can be controlled
via the tooltipPlacement
prop with the following options: left
, right
, bottom
, top
.
Accessing the Form state
The Form
's childen has access to its state to determine how it wants to render.
For example, one field can determine whether to hide or show another field.
Or use one field to determine the contents of another field.
Client-side Validation
In this next example, we do two kinds of client-side validation.
First, we define a validator
prop on the first field to make sure that the password's length is 8 or
more characters. validator
functions take in the current value of the field that they are validating and
return either an error message (if there is a validation error) or a falsy value (if there is no error).
Next, to construct the "Save Password" button, we look at the current form state and render the button as
disabled when state.current.password1
and state.current.password2
do not match.
The field is validated as the user enters a value. When the value is invalid, the canSubmit
function will return false
. The value will only be shown to be invalid after that field loses focus. As soon as the the value becomes valid again, canSubmit
will return true
, and the value will be shown to be valid again.
Due to the above behavior, we recommend against using a validator on the final field of a Form
. The experience can be jarring when a user wants to click the Submit button, but is unable due to a validation error that will only be shown after the field loses focus.
Field id
s & label for
s
All fields require an id
. If you do not provide one, a unique id
will be generated for you. It is used to
set the for
attribute on the corresponding <label>
tag, so that the label is semantically connected to a specific
input.
<Form {...{
fields: {
host: {label: 'Host'},
path: {label: 'Path', children: <Input id="the-path"/>}
}
}}>
{({fields}) => (
<div>
{fields.host}
{fields.path}
</div>
)}
</Form>
Composite fields
When the state of a field is best represented by a collection (e.g. an array or an object), use a composite field.
An initial value must be provided in order for the Reset
button to work properly.
onChange
An onChange
callback needs to be provided for each input element. This callback should use the Form
's onChange
function to update its value. In the example below, both inputs have their own onChange
callback.
Use stopPropagation
within onChange
to stop the Form
from overriding its composite value.
Form submission
Define an onSubmit
prop on a Form
to do something with the state values on submission.
The onSubmit
method is passed {state: {initial, current}}
.
The canSubmit
function is available to help determine whether a form is ready for submission.
It returns true
if all required fields are filled and if all fields are different from their initial value.
By default, a button within the Form
that has type="submit"
will trigger submission. This behavior can also be attached to another field that takes in the onSubmit
, as shown below.
Form error handling
Define a onSubmitError
handler to map error messages to a specific field. Return an object keyed by the field's name
to determine where the error is shown.
Using the FormUnit
To lay out a single form field without using a whole Form
component, you can use the FormUnit
component. The FormUnit
component can decorate a field with a label, a tooltip, an "optional" indicator, and help text.
Note that state management and other Form
features are not handled by the FormUnit
.
Examples
Props
Form props
Property | Required | Type | Default | Description |
---|---|---|---|---|
afterSubmit | no | function | () => {} | Called with {state, setState, response, reset} . response is the return value of onSubmit . |
children | no | node or function | Called with {fields, canSubmit, canReset, reset, onSubmit, setState, state, onChange, onBlur, onChangeCheckbox} | |
fields | no | object | A collection of the inputs to track. | |
onModified | no | function | () => {} | Called on every state change. Called with true when current state is different from initial state. false when they are the same. |
onSubmit | no | function | () => {} | Called with the state, {initial, current} . If this function is async, we will await it. |
onSubmitError | no | function | () => {} | Called with any error that onSubmit throws. Should return object mapping from field name -> String. |
resetOnSubmit | no | bool | If true, resets the form to its initial state after onSubmit succeeds. |
FormUnit props
Property | Required | Type | Default | Description |
---|---|---|---|---|
children | no | node | Input field to decorate with label | |
className | no | string | Class name to attach to top-level div | |
fieldRowClassName | no | string | Class name to attach to the inner div surrounding the field | |
hasError | no | boolean | If true, applies error CSS. Turns border and help text red. | |
help | no | node | Help block shown underneath the field | |
hideHelpRow | no | boolean | false | Hides the help/error block underneath the field, so it does not take up any space |
inline | no | boolean | false | If true positions the label on the same line as the field. |
label | no | string | Text to use for field label | |
labelClassName | no | string | Class name to attach to the inner label element | |
labelFor | no | string | Value of the label's for attribute. If not provided, defaults to the field's id . | |
labelPosition | no | oneOf(['after']) | If after and inline=true positions the label after the field. | |
labelRowClassName | no | string | Class name to attach to the inner div surrounding the label | |
optional | no | boolean | false | If true , marks a field as optional and adds optionalText to label |
optionalText | no | string | '(Optional)' | Text to add to label when field is optional |
postLabel | no | oneOf(node, function) | Content to place in the top right of a non-inline FormUnit , or a callback, called with {state, setValues} , returning that content | |
retainLabelHeight | no | boolean | false | For fields without a label, add an empty space above the field to preserve the space where the label would be. |
setValues | no | function | Function passed to postLabel callback | |
state | no | object | Object passed to postLabel callback | |
tooltip | no | node | Content to place on the tooltip | |
tooltipPlacement | no | oneOf('top', 'bottom', 'left', 'right') | 'top' | Placement of tooltip in relation to icon |
tooltipSize | no | oneOf(['sm', 'md', 'lg']) | 'md' | Size of tooltip |
Imports
Import React components (including CSS):
import {Form, FormUnit} from 'pivotal-ui/react/forms';
Import CSS only:
import 'pivotal-ui/css/forms';