Form

React

Form composes together:

  • formik state management
  • core-react Input, Select, etc. components
  • Show and edit views, discretely known as create, read, update
  • Two layout styles
  • Conditionally rendered components, noted by a third subcomponent Form.Field.Create pattern

Usage

Props

Form

The provider from Formik. Additional props from Formik's Formik as well as the ones stated here.

NameTypeRequiredDefaultDescription
disabledbooleanfalsefalse
Disable all fields at once.
enableConfirmNavigationbooleanfalsefalse
Enable a browser confirmation about losing of unsaved data when the form values are visually different from the initial values. Uses
window.onbeforeunload
initialValuesobjectfalse
Initial values that will align with the fields
name
prop.
onSubmitfunctionfalse
Submit function from formik
(values, actions) => void
viewoneOffalsecreate
Determines show or edit state of form.
oneOf: ['create', 'read', 'update']
variantoneOffalse
Toggle between modern and traditional form styles. Leave blank for the modern label-above layout.
oneOf: ['traditional']
validationSchemaobjectfalse
A Yup object schema to validate all values.
Yup.object().shape({ input_name: Yup.mixed().required() })
This is a great way to get error messages into the component. In addition, if using Yup for required, those fields will automatically get the required asterisk and highlight error.

Form.Form

Formik's Form component.

This component toggles between a form tag on edit views and a div on show views. When on read, the div will only apply className and style props.

More information about steps of submission.

Leveraging the HTML form element allows simple form submission. The Form.Form component will automatically receive the Form's onReset and onSubmit and apply it to the form tag. Leveraging the form tag, we can use a button with type='submit' for submission. Otherwise it will be required to call the useFormContext hook or use <Form> as a render prop function to access the submit handler.

A nicety of leveraging the HTML spec inside React like this:

An app can freely change all the children (React or DOM nodes) of a form, and as long as the Provider is still present in the React tree, all the values will appear in onSubmit when the button is triggered.

Rendering a form onto the document around input components is more semantically correct. Assistive technologies and browser plugins can discover form elements and implement special hooks to make them easier to use [source].

Note: It's strictly forbidden to nest a form inside another form. Nesting can cause forms to behave in an unpredictable manner based on the browser that is being used. [source]

Form.ErrorBanner

Contextual conditional error banner. Will only display when errors are present and contains predifined text for create and update views. Will not display on the read view.

NameTypeRequiredDefaultDescription
itemstringtrue
The item type the form is working on. E.G. Observation, RFI, Report
i18nScopestringfalse

Form.Row

Each row is a CSS grid, based on a 12 column system. Set the top level Form variant='traditional' to change the style of the row and its fields.

Fields

All fields take these general props.

Form.Field

NameTypeRequiredDefaultDescription
asoneOfTypefalse
Customize the input component, either a single component for all views or an object with the keys of the views to render on that particular view. This input component will receive the
field
prop. If using TypeScript, these components will need to extend a specific type from Core React, read more about the props per view.

One of:
ReactComponent
or
{ read: ReactComponent, create: ReactComponent, update: ReactComponent }
oneOf: [function, object]
childrenobjectfalse
A subcomponent to render on a particular view.

One or all of:
Form.X.Create, Form.X.Read, Form.X.Update
colStartoneOftrue
Starting location of column. For traditional variant, this does not exist and is not necessary.
oneOf: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
colWidthoneOftrue6
Width of column. For traditional variant, will be either 6 or 12.
oneOf: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
disabledbooleanfalse
Specify disabled. Field disabled replaces the overall Form disabled state.
errorbooleanfalse
requiredbooleanfalse
The required asterisk if not using Yup
required
<Form validationSchema={Yup.object().shape({ input_name: Yup.mixed().required(), })}>
namestringtrue
Key path in store. Accepts
bracket[notation]
or
dot.notation
.
viewoneOffalse
Determines show or edit state of field.
oneOf: ['create', 'read', 'update']
validatefunctionfalse
The validate function from Formik for single field validation.
(value: Value = any) => undefined | string | Promise<Value>

Specific types of fields include:

Form.Text

Field props and Input props.

Form.TextArea

Field props and TextArea props.

Form.Checkboxes

Field props. A series of several checkboxes. Similiar to a multiselect.

NameTypeRequiredDefaultDescription
isDisabledOptionfunctionfalse
Disable a particular option(s) from the list
(option: any) => boolean
isIndeterminateOptionfunctionfalse
Determine if particular options from the list are indeterminate
(option: any) => boolean

Form.Checkbox

Field props and Checkbox props.

Form.Checkbox is about a single checkbox, while the HTML checkbox input type is designed for a series of checkboxes. Traditionally with checkboxes, [] is false while ['on'] is true, where 'on' is the value of each checked input. This pattern does not fit well with boolean backed data. The form state is oriented towards "yes/no" friendliness and is stored as a boolean not array. The value in store will be true or false and the value on the DOM will be "true" or "false".

Form.Currency

Field props and CurrenyInput props.

Form.DateSelect

Field props and DateSelect props.

Unlike DateSelect which only accepts date objects, Form.DateSelect can have a Form initialValue as a date object or string in the ISO format: yyyy-mm-ddThh:mm:ssZ. Like DateSelect, date changes will be a date oject or null. Be careful when providing string values, the string will be the argument to new Date().

The following examples were ran in California during DST

Format with / results in UTC midnight
Format with - results in local midnight
Format ISO results in local midnight
Format zoned time results in UTC midnight
Leading zeros on two digit entries are required

Form.MultiSelect

Field props and MultiSelect props.

Form.GroupSelect

Field props and GroupSelect props.

Form.TieredSelect

Field props and TieredSelect props.

Form.Number

Field props and NumberInput props.

Form.PillSelect

Field props and PillSelect props.

Form.RadioButtons

Field props. A series of several radio buttons. Similiar to a single select.

NameTypeRequiredDefaultDescription
isDisabledOptionfunctionfalse
Disable a particular option(s) from the list
(option: any) => boolean

Form.RichText

Field props and RichText props.

Relies on a single TextEditorProvider being in the React tree.

Form.Select

Field props and some Select props. Form.Select has an API closer to MultiSelect than Select. It uses getters and an array of options. Automatic searching of options and the clear icon are enabled by default.

NameTypeRequiredDefaultDescription
getGroupfunctionfalse
Callback for each entry in
options
to define relation to group in
optgroups
(option: OptionItem) => string | number
getIdfunctionfalse
The id of an option
(option: OptionItem) => string | number

Default:
(option: OptionItem) => option.id
getLabelfunctionfalse
The display label of an option
(option: OptionItem) => string

Default:
(option: OptionItem) => option.label || option.name
groupGetIdfunctionfalse
The id of group
(group: GroupItem) => string | number

Default:
(group) => group.id
groupGetLabelfunctionfalse
The display label of a group
(group: GroupItem) => string

Default:
(group: GroupItem) => group.label || group.name
groupHeaderRendererfunctionfalse
Callback for rendering header for each entry in
optgroups
(group: GroupItem) => React.ReactNode
isSuggestedOptionfunctionfalse
If nothing is selected, suggest this option. From
Select.Option suggested
.
(option: OptionItem) => boolean
optionsarrayOffalse
Array of options for the menu
arrayOf: [object]
optgroupsarrayOffalse
Array of available option groups
arrayOf: [object]
optionRendererfunctionfalse
Callback for rendering each
option
(option: OptionItem) => React.ReactNode
onClearoneOfTypefalse
Callback for when cleared. Default enabled, has clear icon.
oneOf: [boolean, function]
onSearchoneOfTypefalse
Callback for when searching. Default enabled, has seach bar in menu.
oneOf: [boolean, function]
searchComparatorfunctionfalse
Customize how search works
(query: string, value: string) => boolean

Example

Create and Update

There is currently no visual difference between create and update. In the future that could change anywhere within the Form, like displaying meta data only on update views. Today they exist for flexibility of a field in a workflow. More about customizing between the "edit" views can be read in the props per view.

Read

Traditional Example

Create and Update

To achieve the traditional layout, majority of the props are identical. Two props differ, colWidth can only be 6 or 12 and colStart is not necessary. Width will default to 6 and is only necessary to specify 12 if the field should take the entire row.

Read

Props Per View

Using a self-closing tag with a field, e.g. <Form.Select />, it will have uniformity across create, read, and update views, i.e. this field has all the same properties like label and disabled across views. When a field needs to have different props per view, we can customize with the third subcomponent pattern.

Form.Field.Create Form.Field.Read Form.Field.Update

Conditionally rendered subcomponents based on view, a way to differenciate a field based on form state. The same rules apply for Form.Field apply to Form.Select, Form.Text, etc. Using children of Form.Field we can customize the props per view. However, the form will make no assumptions about the number of child and the field on create, read, update view, you need to explicitly place Form.Field.Create, Form.Field.Read, and Form.Field.Update if you want all views once using the Form.Field children API. By placing common props on the parent and specific props on the child, each view can be customized. (Behind the scenes, Form.Field switched from rendering HTML and is now a context provider for shared props).

What is the difference between .Create and .Update? That is for the client to decide! Both .Ceate and .Update use the same layout and input components. Depending on the UX and context of the field in a workflow, aspects might change with the field. If the create and update views are always identical, it is possible to stick to one view variant for both "edit" views.

The third tier subcomponent props are nearly identical to the second tier, Form.Field, except for children. Anything on this level like disabled, view, label will trump props on Form.Field or Form. For example, when the global state is update or disabled, a Form.Select.Update could overwrite it with view='read' or disabled={false}, while all other fields in the form are in their respective .Update view.

Form.Field is capable of letting the client customize the "input component" for a field while keeping the layout closed. The key thing at play is the field API.

field API

input

Based on Formik input. Useful when working with native HTML form elements and accessing the name or value. Core React does not supply the checkbox properties from Formik.

helpers

Directly from Formik helpers. Setter methods tied to the field.

meta

Mostly Formik meta with some Core React customization. The error property is when to show the error, it is currently a combination of Formik's error and touched. The pending error message will be under message.error. Added properties like view, disabled, and required.

messages

From Core React, informational messages about the field. Today it only contains error. It is possible to have a value for messages.error but meta.error be false. This is because the meta is when to notify the user of an error.

Form.Field as prop with a Component

If using TypeScript, these components will need to extend a specific type from Core React, documented below.

Form.Field as prop with a object of Components

It is possible to create custom components for the input and output of a value. Leveraging the as prop as an object with keys of each of the view states, create, read, update. It is recommended to define this object outside of render, as it should be a static object we can keep the same memory reference for React context performance gains. (Outside of render in a functional component means top level in a file, outside of the function scope. The entire function is the render! Moving the object above the return but inside the functional component does not keep the same memory reference each render.)

TypeScript and as prop components

The input and output components for as inside the layout are typed to extend and receive the field prop with the field API. It will be necessary to import and extends the type in an interface. The type of props.field.input.value will default to any. To supply the value's type, pass the type as a generic arg.

Form.Field children prop

The children of a field, e.g. Form.Number, should be another subcomponent of the same field, e.g. Form.Number.Read, to customize props on a particular view.

Form.Field.X as prop

It only makes sense here for as to be a Component. Form.Field.Create will ever only pick the create key from the object.

If you are separating a form into distinct files like Create.jsx, Read.jsx, Update.jsx and hard setting a constant Form view state, each of those files would use a consistent subcomponent.

Form.Field.X children prop

The children at the third level is a more typical React prop, like text, JSX, or a callback function that receives the field API.

Width Examples

Column spans and column placements should align or be confirmed with the Design System.

  • colStart 1 colWidth 6 with colStart 7 colWidth 6
  • colStart 1 colWidth 12

Validation

Validation runs each submit. Failed validation will prevent form submission and update error messages.

Changing when validations run may work, but does not have full support from the Design System. Use at your own risk.

The recommended validation method is Form validationSchema with a Yup schema. They are extremely expressive and allow modeling complex, interdependent validations, or value transformations. Validation on the Form level opposed to Form.Field guarantees fields are validated even if they are unmounted.

Yup Validation

From Formik documentation:

We use Yup for object schema validation. It has an API that's pretty similar to Joi and React PropTypes but is small enough for the browser and fast enough for runtime usage. Because we ❤️ Yup sooo much, Formik has a special config option / prop for Yup object schemas called validationSchema which will automatically transform Yup's validation errors into a pretty object whose keys match values and touched. This symmetry makes it easy to manage business logic around error messages. -- Formik, The Palmer Group

The following examples show some cases of Form validationSchema={yup}, more information can be found from the Yup documentation.

Required Yup Validation

Using a Yup required schema, yup.mixed().required(), will result with a required marker automatically include on the field. The auto include marker feature may not work with complex Yup schemas.

Conditional Yup Validation

Async Yup Validation

validate Props

Not recommended alternatives.

Form validate

I suggest using validationSchema and Yup for validation. However, validate is a dependency-free, straightforward way to validate your forms. -- Formik

Resources:

Form.Field validate

You can run independent field-level validations by passing a function to the validate prop.

Note: The components' validate function will only be executed on mounted fields. That is to say, if any of your fields unmount during the flow of your form, those fields will not be validated during form validation/submission. -- Formik

This cannot be used in collapsible sections.

Resources:

error Prop

The error prop on a field will always be displayed. It is not linked to Formik validation and form submission.

Initial Errors

A form can mount with errors based on the navigation experience or technical constraints.

Submission

A button type='submit' inside the form tag (Form.Form component) will trigger submission. If any validations fail, the submission will not continue.

It is recommended to use the promise path for submission. Promise submission is required when using enableConfirmNavigation to 'clean up' after submission. More about Formik submission.

Confirm Navigation

Setting the prop enableConfirmNavigation to true will have Form apply a window.onbeforeunload listener to prevent losing unsaved changes, and after a successful submission it will remove the listener. The Form must be notified after successful submission by calling the promise success callback in the onSubmit function. The browser warning of unsaved data when leaving a page will not work in Safari.

onSubmit returns Promise

Submission promise will provide a success and an error callback. State changes like toggling isSubmitting and removing the window.onbeforeunload event will be updated automatically in the component.

If the onSubmit function is async and it returns undefined, it will always be considered a success.

If the onSubmit function is synchronous and it returns undefined, it will never reset certain states.