Form Component Specification¶
A group of one or more fields, inputs, lists, or other forms. A form results in a JavaScript object.
Implementation¶
A Form type component is the most complicated component to create. It must do the following:
- Expect one or more children and render them in order. Children that are part of this spec must be treated specially. They must be cloned with additional properties passed to them.
- Track the current form object in internal component state, updating it as
onChangingoronChangeprops of its children are called. (Each form implementation can choose whether to useonChangingoronChangeto update its state, or can make it configurable.) The object keys are derived from thenameproperty of each descendant that is a field, input, list, or form. - By listening for
onChangeandonChangingandonSubmitcalls from descendants, bubble up these calls but also leave in place any listener functions provided by the user (so both would receiveonChangefor example). - Automatically set the
valueproperty for input, list, or form descendants based on the form object value tracked in state, only if child value prop isn’t already set by the user. - Enforce that every input, list, or form must have a
nameproperty that is set to a non-empty string.
Rendering Descendants¶
A form component, when rendering its descendants recursively, must check whether any of them implement this spec, and if so pass additional properties to them. What follows is an explanation of which properties to pass for each component type.
ErrorsBlock (isFormErrors is true)¶
- Check
namesarray prop - If
errorsprop isundefined, pass the form'serrorsarray, filtered to only those wherenameexactly matches one or more of the strings in thenamesarray. Ifnamesis falsy, do not pass any errors.
Input (isFormInput is true)¶
- Check
namestring prop - If no name, ignore
- Pass functions for
onChangeandonChangingprops: - Retain any functions already supplied for those props and call them first.
- Update the form's value object using lodash.set, the value (first argument), and the
nameprop.set(obj, name, value); - Examine
validateOnandrevalidateOnto determine whether the object should be revalidated. If so, call the form validate function. - Call the form's
onChangeoronChangingas required, passing both the new form object and the validity boolean as arguments. - Pass functions for
onSubmitprop - First call the user-supplied
onSubmitfor the component, if there is one - Then call the form submit function
- If
valueprop isundefined, pass in the current valueget(formValueObject, name) - If
errorsprop isundefined, pass the form'serrorsarray, filtered to only those where the errornameeither exactly matches the componentnameor starts with the componentnameplus a dot or open bracket. Ifnameis falsy, do not pass any errors. - If
isReadOnlyprop is a function, call it passing the current form value and pass the return value to the component instead
Form (isForm is true)¶
Same logic as Input but no isReadOnly prop
FormList (isFormList is true)¶
Same logic as Input but no isReadOnly prop
Field (isFormField is true)¶
- If
errorsprop isundefined, pass the form'serrorsarray, filtered to only those where the errornameeither exactly matches the Fieldnameor starts with the Fieldnameplus a dot or open bracket. Ifnameis falsy, do not pass any errors.
Properties¶
ALL properties are optional, but the name property is required on forms that are nested under other forms. The properties listed here are governed by this specification, but components are free to add any number of additional properties as necessary.
errors¶
PropTypes.arrayOf(PropTypes.shape({
message: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}))
Array of error objects for all inputs on this form, as returned from the validator function.
A top-level form would track the errors array in state while nested forms would receive them as props (since validation is done by the highest level form). A form component must handle the case where errors come from props and state by merging the two arrays into one before passing down to descendants.
onChanging¶
PropTypes.func
As any input anywhere within the form or any of its subforms is changing, the form must call onChanging(newValue, isValid), where newValue is the new value of the form object after the most recent user entry.
onChanging must never be called with the exact same value as the last time it was called.
onChange¶
PropTypes.func
After any input anywhere within the form or any of its subforms has finished changing, the form must call onChange(newValue, isValid), where newValue is the new value of the form object after the most recent user entry.
onChange must never be called with the exact same value as the last time it was called.
onChange must never be called with a newValue that hasn't been first passed to onChanging. (In other words, consumers can safely use ONLY onChanging without fear of missing any change.)
value¶
PropTypes.obj
If the form is editing an existing object, it will be provided here. This is a basic JavaScript object which you can expect to match the structure of the object that the form component builds and passes to onChanging, onChange, and onSubmit.
While we recommend that any change to this value should reset the internally stored, work-in-progress value, it is not required. If you update the value in state, you must call onChanging and onChange with the new value.
validateOn¶
PropTypes.oneOf(['changing', 'changed', 'submit'])
The form must validate prior to calling one of these event functions. Default property value must be "submit". To skip validation, a user would simply not provide a validator function.
revalidateOn¶
PropTypes.oneOf(['changing', 'changed', 'submit'])
This is like validateOn but this value is used instead of validateOn whenever the form has already been validated once since it was first mounted or since the resetValue instance method was called. Default property value must be "changing".
validator¶
PropTypes.func
The form must call validator(value) whenever it needs to validate. It needs to validate whenever validateOn and revalidateOn props say that it should.
hasBeenValidated¶
PropTypes.bool
If this property is set, it must override the internal tracking of whether the validator function has been called. This is used by parent forms when they are handling the validation for a child form.
Static Properties¶
defaultProps [REQUIRED]¶
You must include the defaultProps static property, even if it is only an empty object.
isForm [REQUIRED]¶
Set this to true so that other components know that your component implements the Form specification.
Instance Properties¶
isDirty() [OPTIONAL]¶
Returns a boolean indicating whether anything has been entered/changed by the user.
getValue() [OPTIONAL]¶
Returns the current value of the form in state
validate() [REQUIRED]¶
This function must call the validator function and then update the errors tracked in state. It must also return a Promise that resolves with the errors array.
See the ReactoForm Form component's validate function for an example.
submit() [REQUIRED]¶
This function must call the validator function and then potentially call the onSubmit function.
The object to pass to both functions is the most recent object representing all the form values, which you should be tracking in state. A form object may not be passed to onSubmit unless it has first been passed to both onChanging and onChange.
- If
validatorprop isn't a function, exit. - Call
validatorand expect it to return an array of error objects a Promise that resolves with an array of error objects. - If
validatorrejects or throws, optionally log the error, and then exit. - Store the errors array for passing down to child components.
- If the errors array is empty or (the errors array is non-empty and
shouldSubmitWhenInvalidistrue), callonSubmit. - If
onSubmitreturns or resolves withundefined,null, or an object with propertyokset totrue, consider the submission successfully completed. Call theresetValuemethod, and then exit. - If
onSubmitreturns or resolves with an object with propertyokNOT set totrue, consider the submission failed. If the result object also contains anerrorsproperty set to an errors array, store the errors array for passing down to child components. Exit without calling theresetValuemethod. - If
onSubmitrejects or throws, optionally log the error, and then exit. Do not callresetValuemethod.
See the ReactoForm Form component's submit function for an example.
resetValue() [REQUIRED]¶
This function must do the following:
- Reset the value state to match the value prop
- Clear all validation errors in state
- Call
element.resetValue()on all descendant Inputs, Forms, and FormLists, down to exactly one level of nesting.