UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

493 lines (389 loc) 12.6 kB
--- title: 'Form.useData' description: '`Form.useData` lets you access or modify your form data outside of the form context within your application.' version: 10.104.0 generatedAt: 2026-04-17T18:46:12.396Z checksum: 090b7d977ba4be5e2c4c04d199a30a4048416c59f443a56985df2f80629d9c40 --- # Form.useData ## Import ```tsx 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: ```tsx 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> ) ``` - `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). ## 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](/uilib/extensions/forms/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. ```tsx 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: ```jsx 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: ```jsx 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}>...</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 ```jsx 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](/uilib/extensions/forms/getting-started/#what-is-a-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. ```jsx 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 value update('/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. ```jsx import { Form, Field } from '@dnb/eufemia/extensions/forms' const myFormId = 'unique-id' // or a function, object or React Context reference function 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](/uilib/extensions/forms/Form/Handler/demos/#visible-data) in the demo section. ```tsx import { Form } from '@dnb/eufemia/extensions/forms' function MyComponent() { const { data, reduceToVisibleFields } = Form.useData() // Use useEffect to ensure we get the latest data React.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. ```ts reduceToVisibleFields(data, { keepPaths: ['/foo'], removePaths: ['/bar'], }) ``` ## Filter data You can use the `filterData` function to filter your data. Check out [the example below](#filter-your-data). You simply give it the [same kind of filter](/uilib/extensions/forms/Form/Handler/demos/#filter-your-data) as you would within the `onSubmit` event callback. The callback function receives the following properties in an object: - `path` The path of the field. - `value` The value of the field. - `displayValue` The displayed value of the field. - `label` The label of the field. - `props` The given field properties. - `error` The error of the field. Is `undefined` if 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 caseand 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. ```tsx 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 reference const MyForm = () => { const { filterData } = Form.useData(myFormId) const filteredData = filterData(filterDataHandler) return ( <Form.Handler id={myFormId}> <Field.String path="/foo" data-exclude-field /> </Form.Handler> ) } ``` ```tsx 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](/uilib/extensions/forms/Form/setData/) method – or even in each Field, with the value property. ```jsx import { Form, Field } from '@dnb/eufemia/extensions/forms' const myFormId = 'unique-id' // or a function, object or React Context reference const 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 ```tsx 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). ```tsx 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 ```tsx 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. ```tsx const MyForm = () => { const { data, reduceToVisibleFields } = Form.useData() // Use useEffect to ensure we get the latest data React.useEffect(() => { console.log( 'Result of reduceToVisibleFields:\n', 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 />) ``` ### 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 is hidden. ```tsx 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 />) ```