Skip to content

Form data and state#

State management#

While you can use a controlled approach to handle field or form state, we recommend a declarative approach where you keep the form state inside the data context instead of managing it with your own useState hooks (imperative).

  • You do not need React useState to handle your form data.

How does this work?

Let's say you have a form with a name field. In order to tell, e.g., the onSubmit event what the data structure is, you define a declaration (path) about it right on the field itself:

<Field.Name path="/name" />

Why call it path?

Because it can describe the structure of the data in several layers deep:

<Field.Name path="/user/name" />

Which results in the following data structure:

{
user: {
name: 'Hanna'
}
}

Paths of fields (path="/dataPath") must be unique to ensure proper error handling and data input. If Eufemia detects duplicate paths, it will print the following warning in the console: Eufemia Path declared multiple times: /duplicatePath.

This is called a JSON Pointer—a standardized way of pointing to a specific part of a JavaScript/JSON object.

These JSON pointers are used to both read and write data and to validate form data.

What is a JSON Pointer?#

A JSON Pointer is a string of tokens separated by / characters, these tokens either specify keys in objects or indexes in arrays.

const data = {
foo: {
bar: [
{
baz: 'value',
},
],
},
}
const pointer = '/foo/bar/0/baz' // points to 'value'

You can make these paths type-safe with autocomplete and compile-time checking — see Type-checked paths.

Data handling#

How do I handle complex data logic?

You can show or hide parts of your form based on your own logic. This is done by using the Form.Visibility component (yes, it can even animate the visibility).

You can access and modify your form data with Form.useData, Form.useDataValue, Form.useDataWithoutSubscription, Form.getData, or Form.setData.

Use Form.useDataValue when rendered output depends on one form data path inside Form.Handler. Inside custom field or value components that render form data, use useFieldProps or useValueProps. They subscribe only to the component's own path, while Form.useDataWithoutSubscription is for non-subscribing imperative reads and writes, such as getData() in callbacks or effects.

Here is an example of how to use these methods:

import { Form } from '@dnb/eufemia/extensions/forms'
const myFormId = 'unique-id' // or a function, object or React Context reference
function MyForm() {
return (
<Form.Handler id={myFormId}>
<MyComponent />
</Form.Handler>
)
}
function MyComponent() {
const companyName = Form.useDataValue('/companyName')
const {
getValue,
update,
remove,
set,
data,
filterData,
reduceToVisibleFields,
} = Form.useData() // optionally provide an id or reference
// Use Form.useDataWithoutSubscription() when you need
// imperative helpers without subscribing to data changes.
return <output>{companyName}</output>
}
// You can also use the setData:
Form.setData(myFormId, { companyName: 'DNB' })
// ... and the getData method whenever you need to:
const { getValue, data, filterData, reduceToVisibleFields } =
Form.getData(myFormId)
  • getValue will return the value of the given path.
  • update will update the value of the given path.
  • remove will remove the given path from the data context (fields will reapply their values afterwards).
  • set will set the whole dataset.
  • data will return the whole dataset (unvalidated).
  • filterData will filter the data based on your own logic.
  • reduceToVisibleFields will reduce the given data set to only contain the visible fields (mounted fields).

As you can see in the code above, you can even handle the state outside the Form.Handler context. You find more details on this topic in the Form.useData documentation.

Bedrift

Ønsket sted for tilsendt post

Filter data#

In this section we will show how to filter out some data based on your own logic. You can filter data by any given criteria.

Concepts of filtering data#

There are many ways to utilize filterData, here are some concepts to exclude a data entry:

  • When a field is disabled.
  • When a field is hidden.
  • When a field has an error.
  • When a field has a value that matches a given criteria.
  • When a field has a path that starts with e.g. _.

Here is an example of how to filter data when a path starts with _:

const filterDataHandler = ({ path }) => {
if (/\/_/.test(path)) {
// When returning false, the data entry will be left out
return false
}
}
render(
<Form.Handler
defaultData={{
foo: 'foo',
_bar: 'bar', // Will be excluded
}}
onSubmit={(data, { filterData }) => {
const filteredData = filterData(filterDataHandler)
console.log(filteredData) // Will be { foo: 'foo' }
}}
>
<MyComponent />
</Form.Handler>
)

How do I use it?#

You can utilize the filterData method in:

You can provide either an object with the paths (1) or a handler (2).

Here is an example of how to filter data outlining the different ways to filter data:

// 1. Method – by using paths (JSON Pointer)
const filterDataPaths = {
'/foo': ({ value, data, props, error }) => value !== 'bar',
'/foo': false, // Will exclude the field with the path "/foo"
'/array/0': false, // Will exclude the first item of the array
'/array/*/objectKey': false, // Will exclude all objects inside the array with a key of "objectKey"
}
// 2. Method – by using a handler.
// It will iterate over all fields and data entries regardless what path they have.
const filterDataHandler = ({ path, value, data, props, error }) => {
if (/\/_/.test(path)) {
// When returning false, the data entry will be left out
return false
}
}
const MyComponent = () => {
const { filterData } = Form.useData()
const filteredDataA = filterData(filterDataPaths)
const filteredDataB = filterData(filterDataHandler)
console.log(filteredDataA)
console.log(filteredDataB)
return <Field.String path="/foo" />
}
render(
<Form.Handler>
<MyComponent />
</Form.Handler>
)

You may check out an interactive example of how to filter data.

Filter data during submit#

When submitting data to the server, you might want to exclude data that has been hidden (unmounted) by the user. You have two built-in options to achieve this:

User entered data will always be stored internally in the data context, even if a Field is temporarily shown (mounted/unmounted).

The reduceToVisibleFields method#

You can use the reduceToVisibleFields function to get only the data of visible (mounted) fields. Check out the example in the demo section.

The filterData method#

For filtering data during form submit you can use the filterData method given as a parameter to the onSubmit (Form.Handler) event callback:

render(
<Form.Handler
onSubmit={(data, { filterData }) => {
// Same method as in the previous example
const filteredDataA = filterData(filterDataPaths)
const filteredDataB = filterData(filterDataHandler)
console.log(filteredDataA)
console.log(filteredDataB)
}}
>
<Field.String path="/foo" />
</Form.Handler>
)

You may check out an interactive example of how to filter data during the submit event.

Transforming data#

Each Field.* and Value.* component supports transformer functions.

These functions allow you to transform the field value to a different format than it uses internally, and vice versa.

<Field.String
path="/myField"
transformOut={transformOut}
transformIn={transformIn}
/>
  • transformOut (out of the Field.* component) transforms the internal value before it gets forwarded to the data context or returned as e.g. the onChange value parameter.
  • transformIn (in to the Field.* or Value.* component) transforms the external value before it is displayed and used internally.

Complex objects in the data context#

If you need to store complex objects in the data context instead of simple values like strings, numbers, or booleans, you can use transformer functions.

Suppose you want to store a country object instead of just a country code like NO or SV when using Field.SelectCountry.

You can achieve this by using the transformIn and transformOut functions:

import { Field } from '@dnb/eufemia/extensions/forms'
import type { CountryType } from '@dnb/eufemia/extensions/forms/Field/SelectCountry'
// From the Field (internal value) to the data context or event parameter
const transformOut = (internal, country) => {
if (internal) {
return country
}
}
// To the Field (from e.g. defaultValue)
const transformIn = (external: CountryType) => {
return external?.iso || 'NO'
}
const MyForm = () => {
return (
<Form.Handler>
<Field.SelectCountry
path="/country"
transformIn={transformIn}
transformOut={transformOut}
defaultValue="NO"
/>
<Value.SelectCountry path="/country" transformIn={transformIn} />
</Form.Handler>
)
}
Suggest an edit