Skip to content

Commit

Permalink
feat(Form.useData): add hook to get forms data outside of the context (
Browse files Browse the repository at this point in the history
…#3218)

How to use it:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
function Component() {
  const { data, update } = Form.useData('unique', initialData = undefined)

  return (
    <Form.Handler
      id="unique"
      ...
    >
      ...
    </Form.Handler>
  )
}
```
  • Loading branch information
tujoworker authored Jan 16, 2024
1 parent 9d199bd commit 58c77cd
Show file tree
Hide file tree
Showing 25 changed files with 1,006 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ breadcrumb:
href: /uilib/extensions/forms/
---

import * as Examples from './forms/Examples'
import InlineImg from 'dnb-design-system-portal/src/shared/tags/Img'
import FormDiagram1 from 'Docs/uilib/extensions/forms/form-diagram-1.png'
import FormDiagram2 from 'Docs/uilib/extensions/forms/form-diagram-2.png'
Expand Down Expand Up @@ -66,6 +67,24 @@ render(
)
```

Use the [useData](/uilib/extensions/forms/extended-features/Form/useData/) hook to access or modify your form data outside of the form context within your application:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
function Component() {
const { data, update } = Form.useData('unique')

return (
<Form.Handler
id="unique"
...
>
...
</Form.Handler>
)
}
```

## Philosophy

Eufemia Forms is:
Expand All @@ -88,7 +107,8 @@ In summary:

- Ready to use data driven form components.
- All functionality in all components can be controlled and overridden via props.
- State management using the declarative [JSON Pointer](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03) directive (i.e `path="/firstName"`).
- State management using the declarative [JSON Pointer](/uilib/extensions/forms/#what-is-a-json-pointer) directive (i.e `path="/firstName"`).
- State can be handled outside of the Form.Handler (Provider Context) with the [useData](/uilib/extensions/forms/extended-features/Form/useData) hook.
- Simple validation (like `minLength` on text fields) as well as advanced and complex [Ajv](https://ajv.js.org/) JSON schema validator (Ajv is like Joi or Yup – check out [some examples](/uilib/extensions/forms/extended-features/#schema-validation)) support on both single fields and the whole data set.
- Building blocks for [creating custom field components](/uilib/extensions/forms/create-component).
- Static [value components](/uilib/extensions/forms/extended-features/Value/) for displaying data with proper formatting.
Expand Down Expand Up @@ -151,6 +171,10 @@ The data-driven [base field components](/uilib/extensions/forms/base-fields) are

On top of these, a number of [feature fields](/uilib/extensions/forms/feature-fields) have been built that have special functionality based on given types of data, such as bank account numbers, e-mails and social security numbers.

### Value components

Beside the interactive [Field](/uilib/extensions/forms/fields/) components, there are also the static [Value](/uilib/extensions/forms/extended-features/Value/) components. Use these to show summaries or read-only parts of your application with benefits such as linking to source data and standardized formatting based on the type of data to be displayed.

### Layout

When building your application forms, preferably use the following layout components. They seamlessly places all the fields and components of Eufemia Forms correctly into place.
Expand All @@ -162,13 +186,32 @@ When building your application forms, preferably use the following layout compon

To build an entire form, there are surrounding components such as [Form.Handler](/uilib/extensions/forms/extended-features/Form/Handler) and [StepsLayout](/uilib/extensions/forms/extended-features/StepsLayout) that make data flow and layout easier and save you a lot of extra code, without compromising flexibility.

#### Related topics
### State management

- [Best Practices on Forms](/uilib/extensions/forms/best-practices-on-forms/).
The state management is done via the [JSON Pointer](/uilib/extensions/forms/#what-is-a-json-pointer) directive (i.e `path="/firstName"`). This is a standardized way of pointing to a specific part of a JavaScript/JSON object. The JSON Pointer is used to both read and write data, and is also used to validate the data.

### Value components
<Examples.GettingStarted />

Beside the interactive [Field](/uilib/extensions/forms/fields/) components, there are also the static [Value](/uilib/extensions/forms/extended-features/Value/) components. Use these to show summaries or read-only parts of your application with benefits such as linking to source data and standardized formatting based on the type of data to be displayed.
### What is a JSON Pointer?

A [JSON Pointer](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03) is a string of tokens separated by `/` characters, these tokens either specify keys in objects or indexes into arrays.

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

### Best Practices

- [Best Practices on Forms](/uilib/extensions/forms/best-practices-on-forms/).

## Create your own components

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,65 @@ export const CreateBasicFieldComponent = () => {
)
}

export const GettingStarted = () => {
return (
<ComponentBox hideCode>
{() => {
const existingData = {
companyName: 'DNB',
companyOrganizationNumber: '123456789',
postalAddressSelect: 'companyAddress',
}

function Component() {
const { data } = Form.useData('company-form')
console.log('State:', data)

return (
<Form.Handler
id="company-form"
data={existingData}
onChange={console.log}
onSubmit={console.log}
>
<Flex.Stack>
<Form.MainHeading>Bedrift</Form.MainHeading>
<Card spacing="medium">
<Field.String
path="/companyName"
label="Bedriftens navn"
required
/>
<Field.OrganizationNumber
path="/companyOrganizationNumber"
required
/>
<Field.Selection
path="/postalAddressSelect"
label="Ønsket sted for tilsendt post"
variant="radio"
>
<Field.Option
value="companyAddress"
title="Samme som forretningsadresse"
/>
<Field.Option value="other" title="Annet" />
</Field.Selection>
</Card>
<Form.ButtonRow>
<Form.SubmitButton />
</Form.ButtonRow>
</Flex.Stack>
</Form.Handler>
)
}

return <Component />
}}
</ComponentBox>
)
}

export const CreateComposedFieldComponent = () => {
return (
<ComponentBox
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ Example using the [Form.Handler](/uilib/extensions/forms/extended-features/Form/

```jsx
import { Form, Field, Value } from '@dnb/eufemia/extensions/forms'
render(
<Form.Handler data={existingData} onSubmit={submitHandler}>
<Field.Email path="/email" />
<Value.Date path="/date" />
<Form.SubmitButton />
</Form.Handler>,
)
const existingData={
email: '[email protected]'
date: '2024-01-01'
}
function Component() {
const { data } = Form.useData('unique')

return (
<Form.Handler id="unique" data={existingData} onSubmit={submitHandler}>
<Field.Email path="/email" />
<Value.Date path="/date" />
<Form.SubmitButton />
</Form.Handler>
)
}
```

### Schema validation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const Default = () => {
return (
<ComponentBox>
<Form.Handler
defaultData={{ email: null }}
data={{ email: '' }}
onSubmit={(data) => console.log('onSubmit', data)}
>
<Card spacing="medium">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ render(
)
```

The form data can be handled outside of the form. This is useful if you want to use the form data in other components:

```jsx
import { Form } from '@dnb/eufemia/extensions/forms'
function Component() {
const { data } = Form.useData('unique')

return <Form.Handler id="unique">...</Form.Handler>
}
```

More examples can be found in the [Form.useData](/uilib/extensions/forms/extended-features/Form/useData/) docs.

## Browser autofill

You can set `autoComplete` on the `Form.Handler` – each [Field.String](/uilib/extensions/forms/base-fields/String/)-field will then get `autoComplete="on"`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ breadcrumb:
---

import Info from 'Docs/uilib/extensions/forms/extended-features/Form/Visibility/info'
import Demos from 'Docs/uilib/extensions/forms/extended-features/Form/Visibility/demos'

<Info />
<Demos />
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: 'useData'
description: '`useData` lets you access or modify your form data outside of the form context within your application'
componentType: 'advanced-api'
hideInMenu: true
showTabs: true
tabs:
- title: Info
key: '/info'
- title: Demos
key: '/demos'
breadcrumb:
- text: Forms
href: /uilib/extensions/forms/
- text: Extended features
href: /uilib/extensions/forms/extended-features/
- text: Form
href: /uilib/extensions/forms/extended-features/Form/
- text: useData
href: /uilib/extensions/forms/extended-features/Form/useData/
---

import Info from 'Docs/uilib/extensions/forms/extended-features/Form/useData/info'
import Demos from 'Docs/uilib/extensions/forms/extended-features/Form/useData/demos'

<Info />
<Demos />
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react'
import ComponentBox from '../../../../../../../shared/tags/ComponentBox'
import { Button, Flex } from '@dnb/eufemia/src'
import { Form, Field } from '@dnb/eufemia/src/extensions/forms'

export function Default() {
return (
<ComponentBox>
{() => {
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>
)
}

return <Component />
}}
</ComponentBox>
)
}

export function Update() {
return (
<ComponentBox>
{() => {
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>
)
}

return <Component />
}}
</ComponentBox>
)
}

export function WithoutFormHandler() {
return (
<ComponentBox>
{() => {
const existingData = { count: 1 }

const Component = () => {
const { data, update } = Form.useData(
'idependent-id',
existingData,
)

const increment = React.useCallback(() => {
update('/count', (count) => {
return count + 1
})
}, [update])

return (
<Button
on_click={increment}
text={'Increment ' + data.count}
variant="secondary"
/>
)
}

return (
<Flex.Vertical>
<Component />
<Component />
</Flex.Vertical>
)
}}
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
showTabs: true
---

import * as Examples from './Examples'

## Demos

### Set data outside of the form

<Examples.Default />

### 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).

<Examples.Update />

### Shared state without a Form.Handler

<Examples.WithoutFormHandler />
Loading

0 comments on commit 58c77cd

Please sign in to comment.