Import
import { Form } from '@dnb/eufemia/extensions/forms'render(<Form.Handler />)
Description
The Form.Handler is the root component of your form. It provides a HTML form element and handles the form data.
import { Form } from '@dnb/eufemia/extensions/forms'const existingData = { firstName: 'Nora' }function MyForm() {return (<Form.HandlerdefaultData={existingData}onSubmit={...}>Your Form</Form.Handler>)}
Relevant links
TypeScript support
You can define the TypeScript type structure for your form data. This will help you to get better code completion and type checking.
NB: Use type instead of interface for the type definition.
import { Form } from '@dnb/eufemia/extensions/forms'type MyData = {firstName?: string}// Method #1 – without initial datafunction MyForm() {return (<Form.Handler<MyData>onSubmit={(data) => {console.log(data.firstName satisfies string)}}>...</Form.Handler>)}// Method #2 – with data (initial values)const existingData: MyData = {firstName: 'Nora',}function MyForm() {return (<Form.HandlerdefaultData={existingData}onSubmit={(data) => {console.log(data.firstName satisfies string)}}>...</Form.Handler>)}// Method #3 – type definition for the submit handlerimport type { OnSubmit } from '@dnb/eufemia/extensions/forms'const submitHandler: OnSubmit<MyData> = (data) => {console.log(data.firstName satisfies string)}function MyForm() {return <Form.Handler onSubmit={submitHandler}>...</Form.Handler>}// Method #4 – type definition on the event parameterfunction MyForm() {return (<Form.HandleronSubmit={(data: MyData) => {console.log(data.firstName satisfies string)}}>...</Form.Handler>)}
To disable types you can:
<Form.Handler<any>>...</Form.Handler>
Decoupling the form element
For more flexibility, you can decouple the form element from the form context by using the decoupleForm property. It is recommended to use the Form.Element to wrap your rest of your form:
import { Form } from '@dnb/eufemia/extensions/forms'function MyApp() {return (<Form.Handler decoupleForm><AppRelatedThings><Form.Element><Form.MainHeading>Heading</Form.MainHeading><Form.Card><Field.Email /></Form.Card><Form.SubmitButton /></Form.Element></AppRelatedThings></Form.Handler>)}
Data handling
You can access, mutate and filter data inside of the form context by using the Form.useData hook:
import { Form } from '@dnb/eufemia/extensions/forms'function MyComponent() {const {getValue,update,remove,set,data,filterData,reduceToVisibleFields,} = Form.useData()return <>...</>}function MyApp() {return (<><Form.Handler>...</Form.Handler><MyComponent /></>)}
getValuewill return the value of the given path.updatewill update the value of the given path.removewill remove the given path from the data context (fields will reapply their values afterwards).setwill set the whole dataset.datawill return the whole dataset (unvalidated).filterDatawill filter the data based on your own logic.reduceToVisibleFieldswill reduce the given data set to only contain the visible fields (mounted fields).
Using a form ID
The form data can be handled outside of the form. This is useful if you want to use the form data in other components:
import { Form } from '@dnb/eufemia/extensions/forms'const myFormId = 'unique-id' // or a function, object or React Context referencefunction MyComponent() {const { data } = Form.useData(myFormId)return <>...</>}function MyApp() {return (<><Form.Handler id={myFormId}>...</Form.Handler><MyComponent /></>)}
More examples can be found in the Form.useData hook docs.
Async onChange and onSubmit event handlers
NB: When using an async onChange event handler, the data parameter will only include validated data. This lets you utilize the data parameter directly in your request, without having to further process or transform it.
If you need to use the original data (sync), you can access it via the Form.useData hook.
Depending on your needs, you may want to use e.g. debounceAsync (debounce) to prevent multiple requests from being sent.
You can return parameters from inside the async onChange or onSubmit event handler. This way you can display more related information, such as an error or an object with these keys:
// Async event handlerconst onChange = debounceAsync(async function (data) {try {await makeRequest(data)} catch (error) {return error}// Optionally, you can return an object with these keys, depending your needsreturn {info: 'Info message',warning: 'Warning message',// and either an errorerror: new Error('Error message'),// or success (when used for autosave)success: 'saved',} as const})
In all async operations, you can simply return an error object to display it in the form or influence the form behavior.
import { Form } from '@dnb/eufemia/extensions/forms'const myFormId = 'unique-id' // or a function, object or React Context reference// Async functionconst onSubmit = async (data) => {try {const response = await fetch('https://api.example.com', {method: 'POST',body: JSON.stringify(data),})const data = await response.json()Form.setData(myFormId, data) // Whatever you want to do with the data} catch (error) {return error // Will display the error message in the form}// Optionally, you can return an object with these keys, depending your needsreturn {info: 'Info message',warning: 'Warning message',// Force the form to stay in pending statestatus: 'pending',// and either an errorerror: new Error('Error message'),} as const}function Component() {return (<Form.Handler id={myFormId} onSubmit={onSubmit}>...</Form.Handler>)}
The info, warning and error messages will be displayed at the bottom of a form or field (FormStatus), depending where it is used. While the success will be displayed on the label of the field that initiated the onChange event.
Browser autofill
You can set autoComplete on the Form.Handler – each Field.String-field will then get autoComplete="on":
<Form.Handler autoComplete={true}><Field.String path="/firstName" /><Field.String path="/firstName" /></Form.Handler>
The path property will be used to set the name attribute, which lets browser know which autocomplete value should be proposed to the user.
Temporary storage
The sessionStorageId feature uses the browsers session-storage (temporary storage mechanism) to store data entered by the user.
This lets the user navigate away and come back to the form, without loosing already entered data.
Ensure you only use this feature for non-sensitive data.
It will flush the storage once the form gets submitted.
Visible data
You can use the reduceToVisibleFields function to get only the data of visible (mounted) fields.
import { Form } from '@dnb/eufemia/extensions/forms'render(<Form.HandleronSubmit={(data, { reduceToVisibleFields }) => {const myData = reduceToVisibleFields(data, {keepPaths: ['/foo'],removePaths: ['/bar'],})}}><Form.SubmitButton /></Form.Handler>,)
Filter data
You can use the filterData function to filter your onSubmit data. It might be useful, for example, to exclude disabled fields or filter out empty fields. The callback function receives the following arguments:
The callback function receives the following properties in an object:
pathThe path of the field.valueThe value of the field.displayValueThe displayed value of the field.labelThe label of the field.propsThe given field properties.errorThe error of the field. Isundefinedif there is no error.
The callback function should return a boolean or undefined. Return false to exclude an entry.
It returns the filtered form data.
The Form.useData hook and the Form.getData method also returns a filterData function you can use to filter data the same way.
In the demo section is an example of how to use the filterData method.
Filter arrays
You can filter arrays by using the filterData method. You can find more information about this in the Iterate.Array docs.
onSubmit parameters
The onSubmit event returns additional functions you can call:
filterDataFilters the given/internal data set.reduceToVisibleFieldsReduces the given data set to only contain the visible fields (mounted fields).transformDataWill call your given function for eachField.*that contains a path (notIterate.Array). It's up to you to define the shape of the value.resetFormDeletessessionStorageand browser stored autocomplete data.clearDataEmpties the given/internal data set.
import { Form } from '@dnb/eufemia/extensions/forms'const myFilter = {'/myPath': (value) => {return value.length > 0},}const MyForm = () => {return (<Form.HandleronSubmit={(data,{filterData,reduceToVisibleFields,transformData,resetForm,clearData,},) => {resetForm()clearData()const filteredData = filterData(myFilter)const myData = reduceToVisibleFields(filteredData)const transformed = transformData(myData,({ path, value, displayValue, label, props, error }) => {return 'new value'},)}}sessionStorageId="session-key"><Form.SubmitButton /></Form.Handler>)}
transformData
The transformData handler will call your given function for each Field.* that contains a path (not Iterate.Array). The returned value will be used instead of the given value and returned as a new data object. It's up to you to define the shape of the returned value.
The callback function receives the following properties in an object:
pathThe path of the field.valueThe value of the field.displayValueThe displayed value of the field.labelThe label of the field.propsThe given field properties.errorThe error of the field. Isundefinedif there is no error.
displayValue can be undefined if a field does not support it, or it's value is not set (emptyValue).
Most of the fields will return the displayValue as a string. But there are some exceptions:
- Field.ArraySelection will return the displayed/active options content as an array that contains a string (or React.ReactNode).
displayValue from fields inside Iterate.Array
When using the Iterate.Array component, you may check if the current entry is an array. This way you ensure you never transform the array itself, but only the values from the fields inside the array.
import { Form } from '@dnb/eufemia/extensions/forms'const MyForm = () => {return (<Form.HandleronSubmit={(data, { transformData }) => {const transformedData = transformData(data,({ value, displayValue, label }) => {return { value, displayValue, label }},)}}><Form.Card><Iterate.Array path="/myArray"><Field.String itemPath="/" label="My label" /></Iterate.Array></Form.Card><Form.SubmitButton /></Form.Handler>)}