In previous sections, we learned how to start building the user interface with components and fetching data. One of the most common requirements when implementing a user interface is forms.
To facilitate the development of forms, we provide many ready-to-use input and field components based on the commercetools Design System.
Form state management
Forms generally consist of a group of inputs that users interact with to trigger actions.
In this process, all user interactions must be tracked, managed, and reflected in the UI. To achieve this, a form must maintain its own state.
Formik is used as the preferred form state management library when building user interfaces.
Formik comes with several built-in features, including validation, array fields, input states, and async submission.
At a bare minimum, implementing a form could look something like this:
The commercetools UI Kit library provides several UI components for working with forms. Most of the time you should use the field components as they provide all the recommended features for rendering form elements.
A field component consists of an input element wrapped with other field elements (such as label, description, error message, hint, and badge).
Depending on the use case, you might want to use a date field, a text field, or a select field. In the UI Kit library, there are many components that cover different use cases. For more information about field and input components check their related packages in the commercetools/ui-kit repository.
All UI Kit field components have a related input component, for example <TextField> -> <TextInput>.
All field and input components have built-in support for accessibility features such as aria labels, keyboard navigation, focus management, and error messaging.
Testing these field components with React Testing Library can be easily done using selectors such as *ByLabelText and *ByRole.
One of the important parts of forms is validation. It is important to validate constraints of forms such as required fields and to check additional semantic requirements (for example, checking that the value is a valid URL).
Aside from client-side validation, forms can also perform asynchronous validation against an API to ensure data correctness before form submission.
For that, Formik allows you to implement a validate function that returns an object of errors.
// Similar shape of `FormikErrors` but values are `TFieldErrors` objects.
In this example, we are validating that the name field has a required value. If the value is empty (no localized values have been provided), we assign to the errors.name property an error object with the error key missing set to true.
In the field component, you must assign the errors and touched props which make the field component render an error message, in case one was returned from the validate function.
Error messages are only shown when the touched value for the specific field is true. This happens whenever the user stops interacting with a field (loses focus). The point here is that as long as the user is interacting with a field for the first time there is no need to show validation.
By default, field components have built-in error messages for the missing error key. Any other error message can be mapped and rendered using the renderError function.
Most of the time you would have simple 1:1 mapping. However, we still recommend using these conversion functions as a best practice and to help decoupling the data transformation logic from the form component.
For instance, the form might only need a couple of fields even though the data object has many more. By being explicit in the conversion, we ensure that only the necessary data is passed to the form.
Building a form page
Let's apply what we just learned in our Channels application, as we might want to add a page to view and manage a Channel's details.
Given that we might want to allow creating new channels and updating existing ones, there will be two different pages with the same form components. Therefore, we can implement the form as a component and re-use it in both the create and details pages. The only difference is that the form for the create page will be initially empty and the form for the details page will have some data.
Sometimes a form contains many different fields, resulting in the form component to be difficult to read.
One way to improve that is to split the form component into multiple smaller components. As a rule of thumb you can create one separate component for each form field. For example a <FormChannelNameField>, a <FormChannelKeyField>, and so on.
To use the useField hook, the form must be wrapped with the <Formik> component instead of using the useFormik hook, so that the React context is properly defined.
As a result, the form component can look like this:
// Handle form submission in the parent component.
// Only contains the form elements, no buttons.
const formElements =(
One of the advantages of splitting up the form fields is to encapsulate the logic. You might notice that we don't explicitly pass any props to these components. Instead, each component can access the form data that they need from the form context, using Formik's useField hook.