Create a form

Forms can be freely composed by using the available form field components and optionally wrapping them in FormRow or Form components as desired. This guide describes some of the basic steps to get simple forms up and running quickly. Consider playing around with the different examples on the form example page on the FDS playground to get a better understanding of the possibilities.

Basic API of any form field component

When you look at the API of any form field component you'll notice they all share the same or very similar props. The most important props for using a form field component are:

  • value(s): should be set to either null or an appropriate value for the specific form field component. Check the API docs of your specific form field component for more details.

  • onChange: a callback that is called whenever the internal state of the form field changes as a result of some sort of user input.
    This is always called with the current internal value(s).

  • validate: a callback that is called when the form field is first mounted and whenever the value of the component changes after setting/updating the value(s) prop.
    This is always called with the current internal value(s).

The basic React pattern to implement a form field then becomes:

  1. Store the value of the value(s) prop in the state of a parent component that renders the form field component and pass it to the form field component.

  2. Handle the onChange callback by updating the value(s) in the state to the given value(s) using setState.

  3. Optionally implement a validate callback to return null if all is well or an object with a connotation property set to either ‘warning’, ‘error’ or ‘info’ and an optional message property set to any string.


A simple code example is given below.

Using form field components standalone

The simplest implementation of a form is a single form field without any other components to support it. An example could be a Popover that shows a single autocomplete field to select a value for an attribute of an element.
This could simply be implemented by using a SingleAutocomplete and manually handling the onChange callback and setting the value prop as described in the basic pattern to implement a form field above.

An example of a popover that follows this basic pattern would be:

import React, { useState } from 'react';

import { Popover, PopoverBody, PopoverHeader, SingleAutocomplete } from 'fds/components';

const items = [/* omitted for brevity */];

function ExamplePopover() {
	const [value, setValue] = useState(null);

	return (
		<Popover>
			<PopoverHeader title="Edit some field name" />

			<PopoverBody>
				<SingleAutocomplete
					items={items}
					onChange={setValue}
					value={value}
				/>
			</PopoverBody>
		</Popover>
	);
}

export default ExamplePopover;

Note: validate is not implemented in this case. You could implement it if the value has to be set, but then you would have to make sure there is an initial/fallback value at all times because a single autocomplete can be cleared by the user. That would only complicate this code example.

Showing labels next to form field components

In order to neatly show labels before or above each form field component, a FormRow component can be used. Simply wrap it around your form field component and set its label prop as desired and its labelPosition prop to either ‘before’ or ‘above’. When applied to the earlier example, the render function could return something like this instead:

<Popover>
	<PopoverBody>
		<FormRow label="Edit some field name" labelPosition="above">
			<SingleAutocomplete
				items={items}
				onChange={setValue}
 				value={value}
			/>
		</FormRow>
	</PopoverBody>
</Popover>

Combining multiple form components in a single FormRow

It is also possible to wrap a single form row around multiple form field components. In that case, you can use the FormRow’s spaceVerticalSize prop.
If you like to layout multiple form fields next to each other instead of below each other, simply use a Flex component or any of the other FDS layout containers around those form fields and wrap them in a form row to render the label.

If you don’t want any labels before or above your form field(s), there is no need to use the FormRow component at all.

Using a Form component to aggregate the feedback and values of all fields at once

The main reason to use a Form component is to easily manage multiple related form fields as a single unit of data. This is usually best when the form should be submitted only when it’s complete and has an explicit submit button to do so. This could be the case when the form is submitted to an external API.

If your form field value(s) should be saved to the XML directly, we recommend handling each form field separately.

If you do so, no submit button is necessary and each field can automatically ‘save’ its value directly to the XML. Ideally by executing an appropriate operation whenever the onChange callback of the form field changes.

This approach is stubbed in the ‘No form’ example on the form example on the FDS playground. Along with some other examples of using Form, FormRow and/or custom layout containers.

There are helper functions to aggregate and display all feedback and values.
These are also demonstrated on the form example on the playground. Also the API docs for Form, FormRow, hasFormFeedback and FormFeedbackMessagesList provide additional details.

Extra considerations when writing form field validation functions

Performance

Validate functions are called often (during each re-render of every field in the form) so make sure they are performant and as cheap to execute as possible.
In a future release we will memoize these functions, so repeated calls with the same input value will just use the memoized value instead of running the actual validate function again.

Make sure the validate function is a pure function, meaning it is only dependent on its input value and does not use or modify any external data/state.
This is necessary because you cannot manually trigger form validation and updating any resulting feedback based on external data/state changes.

Related / interdependent fields

The previous note also illustrates why it is currently not (cleanly/easily) possible to make form fields depend on each other. This will be addressed in a future release.

If you need to make a form where fields depend on each other, consider contacting Fonto with your requirements as early as possible so we can take those into consideration when designing the API and UX to do so supported by the SDK.

If you cannot wait for that, you can build it yourself, but you would need to build some of the form functionality yourself as well.

This includes manually tracking and rendering the value and feedback for each field and calling a custom validation routine when handling the onChange callback. 
That might not be very complex but does require a lot of manual imperative ‘bookkeeping’ code. Think lots of callback handling and setState calls, which we would eventually like to implement within the form (field) components and expose through a declarative API, such as props on those components.

To render the feedback yourself, you can manually compose it by using Flex, Icon and Text components around and next to your form field component.

Available form field components

Was this page helpful?