Import
import { Form } from '@dnb/eufemia/extensions/forms'// Use Form.useData
Description
With the Form.useData hook, you can manage your form data from nested components and outside the form context (Form.Handler).
The hook returns an object with the following properties:
import { Form } from '@dnb/eufemia/extensions/forms'function MyComponent() {const {getValue,update,remove,set,data,filterData,reduceToVisibleFields,} = Form.useData()return <>MyComponent</>}render(<Form.Handler><MyComponent /></Form.Handler>,)
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).
Usage
You can use the Form.useData hook with or without an id (string, function, object or React Context as the reference) property, which is optional and can be used to link the data to a specific Form.Handler component.
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.
type MyData = { firstName: string }const MyComponent = () => {const { data } = Form.useData<MyData>()return data.firstName}
Without an id property
Here "Component" is rendered inside the Form.Handler component and does not need an id property to access the form data:
import { Form } from '@dnb/eufemia/extensions/forms'function MyForm() {return (<Form.Handler><Component /></Form.Handler>)}function Component() {const { data } = Form.useData()}
With an id property
While in this example, "Component" is outside the Form.Handler context, but linked together via the id (string, function, object or React Context as the reference) property:
import { Form } from '@dnb/eufemia/extensions/forms'const myFormId = 'unique-id' // or a function, object or React Context referencefunction MyForm() {return (<><Form.Handler id={myFormId}>...</Form.Handler><Component /></>)}function Component() {const { data } = Form.useData(myFormId)}
This is beneficial when you need to utilize the form data in other places within your application.
Select a single value
import { Form } from '@dnb/eufemia/extensions/forms'function MyComponent() {const { getValue } = Form.useData()const value = getValue('/foo')}
Update data
If you need to update the data, you can use the update method.
It takes a path (JSON Pointer) and a callback function. The callback function receives the existing value as the first argument, and the second argument is the path itself. The callback function must return the new value.
import { Form } from '@dnb/eufemia/extensions/forms'function Component() {const { update } = Form.useData()useEffect(() => {update('/foo', 'new value')// - or with a callback function to get the existing valueupdate('/foo', (existingValue) => existingValue + 'new value')}, [])}
Extend the whole data set
With the set method, you can extend the data set. Existing data paths will be overwritten.
import { Form, Field } from '@dnb/eufemia/extensions/forms'const myFormId = 'unique-id' // or a function, object or React Context referencefunction MyForm() {const { data, set } = Form.useData(myFormId)useEffect(() => {set({ foo: 'bar' })}, [])return (<Form.Handler id={myFormId}><Field.String path="/foo" /></Form.Handler>)}
Visible data
You can use the reduceToVisibleFields function to get only the data of visible (mounted) fields. Check out the example in the demo section.
import { Form } from '@dnb/eufemia/extensions/forms'function MyComponent() {const { data, reduceToVisibleFields } = Form.useData()// Use useEffect to ensure we get the latest dataReact.useEffect(() => {console.log(reduceToVisibleFields(data))}, [data])return <>MyComponent</>}render(<Form.Handler><MyComponent /></Form.Handler>,)
In addition, you can include or exclude paths by using the keepPaths and removePaths options.
reduceToVisibleFields(data, {keepPaths: ['/foo'],removePaths: ['/bar'],})
Filter data
You can use the filterData function to filter your data. Check out the example below.
You simply give it the same kind of filter as you would within the onSubmit event callback.
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.
Tip: Depending on your use case – and instead of disabled – you may rather use a data-* attribute on your field (e.g. data-exclude-field) to filter the field out of the data set.
const filterDataHandler = ({ path, value, data, props, error }) => {if (props['data-exclude-field']) {return false}}const myFormId = 'unique-id' // or a function, object or React Context referenceconst MyForm = () => {const { filterData } = Form.useData(myFormId)const filteredData = filterData(filterDataHandler)return (<Form.Handler id={myFormId}><Field.String path="/foo" data-exclude-field /></Form.Handler>)}
const filterDataHandler = ({ path, value, data, props, error }) => {return !(error instanceof Error)}
Initial data
You decide where and when you want to provide the initial data to the form. It can be done via the Form.Handler component, or via the Form.useData Hook or Form.setData method – or even in each Field, with the value property.
import { Form, Field } from '@dnb/eufemia/extensions/forms'const myFormId = 'unique-id' // or a function, object or React Context referenceconst initialData = { foo: 'bar' }function MyForm() {return (<Form.Handler id={myFormId} data={initialData}><Field.String path="/foo" /></Form.Handler>)}function ComponentA() {Form.useData(myFormId, { foo: 'bar' })}function ComponentB() {const { set } = Form.useData(myFormId)useEffect(() => {set({ foo: 'bar' })}, [])}
Validation
tl;dr: the useData hook returns unvalidated data.
When you use an async onChange, onChangeValidator or onBlurValidator event handler on a field, it will delay the "submitted" value, because of its async nature.
That means, if you want to access the value of a field immediately, you can use the useData hook for that, as it always returns unvalidated data, in sync.
Demos
Set data outside of the form
const existingData = { foo: 'bar', } const Component = () => { const { data } = Form.useData('default-id', existingData) return ( <Form.Handler id="default-id"> <Field.String path="/foo" label={data.foo} /> </Form.Handler> ) } render(<Component />)
Update the data outside of the form
The update function update('/count', (count) => count + 1) has TypeScript support and returns the correct type for count (number).
const existingData = { count: 1, } const Component = () => { const { data, update } = Form.useData('update-id', existingData) const increment = React.useCallback(() => { update('/count', (count) => { return count + 1 }) }, [update]) return ( <Form.Handler id="update-id"> <Flex.Horizontal> <Field.Number path="/count" showStepControls /> <Form.SubmitButton onClick={increment} text={`Increment ${data.count}`} /> </Flex.Horizontal> </Form.Handler> ) } render(<Component />)
Shared state without a Form.Handler
const existingData = { count: 1, } const Component = () => { const { data, update } = Form.useData('independent-id', existingData) const increment = React.useCallback(() => { update('/count', (count) => { return count + 1 }) }, [update]) return ( <Button on_click={increment} text={`Increment ${data.count}`} variant="secondary" /> ) } render( <Flex.Vertical> <Component /> <Component /> </Flex.Vertical>, )
Get only data of visible fields
You can use the reduceToVisibleFields function to get only the data of visible (mounted) fields.
const MyForm = () => { const { data, reduceToVisibleFields } = Form.useData() // Use useEffect to ensure we get the latest data React.useEffect(() => { console.log( 'Result of reduceToVisibleFields: ', reduceToVisibleFields(data, { removePaths: ['/isVisible'], }), ) }, [data, reduceToVisibleFields]) return ( <Form.Handler> <Flex.Stack> <Field.Boolean label="Show radio buttons" variant="button" path="/isVisible" defaultValue={true} /> <Form.Visibility pathTrue="/isVisible" animate> <Field.Selection label="Radio buttons" variant="radio" path="/myValue" defaultValue="foo" > <Field.Option value="foo" title="Foo" /> <Field.Option value="bar" title="Bar" /> </Field.Selection> </Form.Visibility> <Value.Selection path="/myValue" inheritLabel inheritVisibility /> </Flex.Stack> </Form.Handler> ) } render(<MyForm />)
SyntaxError: Invalid or unexpected token
Filter your data
This example uses the keepInDOM property to keep the field in the DOM.
But with the filterData method we can filter out all fields that have the data-exclude-field attribute.
In this demo, the data-exclude-field attribute is added when the field are hidden.
const filterDataPaths = { '/isVisible': false, '/mySelection': ({ data }) => data.isVisible, '/myString': ({ data }) => { return data.isVisible && data.mySelection === 'more' }, } const MyForm = () => { return ( <Form.Handler defaultData={{ isVisible: false, mySelection: 'less', myString: 'foo', }} > <Flex.Stack> <Field.Boolean label="Toggle visible" variant="button" path="/isVisible" data-exclude-field /> <Form.Visibility pathTrue="/isVisible" animate> <Field.Selection label="Choose" variant="radio" path="/mySelection" value="less" > <Field.Option value="less" title="Less" /> <Field.Option value="more" title="More" /> </Field.Selection> <Form.Visibility visibleWhen={{ path: '/mySelection', hasValue: 'more', }} animate > <Field.String label="My String" path="/myString" value="foo" /> </Form.Visibility> </Form.Visibility> <Output /> </Flex.Stack> </Form.Handler> ) } const Output = () => { const { data, filterData } = Form.useData() return ( <> <Tools.Log data={filterData(filterDataPaths)} label="Filtered:" /> <Tools.Log data={data} label="All data:" /> </> ) } render(<MyForm />)