Skip to content

Commit

Permalink
feat(Forms): enhance typing and add docs on how to deal with TypeScri…
Browse files Browse the repository at this point in the history
…pt types (#4343)

The main change is that we go from:

```tsx
type JsonObject = any
```

to

```tsx
type JsonObject = Record<string | number, unknown> | Array<unknown>
```

and enhance the TypeScript support docs for the "Getting Started"
section the `Form.Handler` and `Form.Isolation` docs.

To disable types you can do so by:


```tsx
<Form.Handler<any>>
...
</Form.Handler>
```

or

```tsx
const { data } = Form.useData<any>()
```
  • Loading branch information
tujoworker authored Nov 29, 2024
1 parent 4a0fea1 commit 10b199b
Show file tree
Hide file tree
Showing 23 changed files with 309 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@dnb/eufemia/src/extensions/forms'
import { Flex } from '@dnb/eufemia/src'

export const TestdataSchema: JSONSchema = {
export const TestDataSchema: JSONSchema = {
type: 'object',
properties: {
requiredString: { type: 'string' },
Expand Down Expand Up @@ -37,7 +37,7 @@ export const TestdataSchema: JSONSchema = {
required: ['requiredString'],
}

export interface Testdata {
export type TestData = {
requiredString: string
string?: string
number?: number
Expand All @@ -53,7 +53,7 @@ export interface Testdata {
}>
}

export const testdata: Testdata = {
export const testData: TestData = {
requiredString: 'This is a text',
string: 'String value',
number: 123,
Expand Down Expand Up @@ -81,12 +81,12 @@ export const Default = () => {
scope={{
DataContext,
Value,
testdata,
TestdataSchema,
testData,
TestDataSchema,
}}
>
<DataContext.Provider
defaultData={testdata}
defaultData={testData}
onChange={(data) => console.log('onChange', data)}
onPathChange={(path, value) =>
console.log('onPathChange', path, value)
Expand Down Expand Up @@ -177,13 +177,13 @@ export const ValidationWithJsonSchema = () => {
scope={{
DataContext,
Value,
testdata,
TestdataSchema,
testData,
TestDataSchema,
}}
>
<DataContext.Provider
data={testdata}
schema={TestdataSchema}
data={testData}
schema={TestDataSchema}
onChange={(data) => console.log('onChange', data)}
onPathChange={(path, value) =>
console.log('onPathChange', path, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import AsyncChangeExample from './parts/async-change-example.mdx'

The `Form.Handler` is the root component of your form. It provides a HTML form element and handles the form data.

```jsx
```tsx
import { Form } from '@dnb/eufemia/extensions/forms'

const existingData = { firstName: 'Nora' }
Expand All @@ -26,6 +26,75 @@ function MyForm() {
}
```

### TypeScript support

You can define the TypeScript type structure for your data like so:

```tsx
import { Form } from '@dnb/eufemia/extensions/forms'

type MyDataContext = {
firstName?: string
}

// Method #1 – without initial data
function MyForm() {
return (
<Form.Handler<MyDataContext>
onSubmit={(data) => {
// "firstName" is of type string
console.log(data.firstName)
}}
>
...
</Form.Handler>
)
}

// Method #2 – with data (initial values)
const existingData: MyDataContext = {
firstName: 'Nora',
}
function MyForm() {
return (
<Form.Handler
defaultData={existingData}
onSubmit={(data) => {
// "firstName" is of type string
console.log(data.firstName)
}}
>
...
</Form.Handler>
)
}

// Method #3 – type definition on the event parameter
const submitHandler = (data: MyDataContext) => {
// "firstName" is of type string
console.log(data.firstName)
}
function MyForm() {
return <Form.Handler onSubmit={submitHandler}>...</Form.Handler>
}

// Method #4 – type definition for the submit handler
import type { OnSubmit } from '@dnb/eufemia/extensions/forms'
const submitHandler: OnSubmit<MyDataContext> = (data) => {
// "firstName" is of type string
console.log(data.firstName)
}
function MyForm() {
return <Form.Handler onSubmit={submitHandler}>...</Form.Handler>
}
```

To disable types you can:

```tsx
<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:
Expand Down Expand Up @@ -116,51 +185,6 @@ function MyApp() {

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

### TypeScript support

You can define the TypeScript type structure for your data like so:

```tsx
import { Form } from '@dnb/eufemia/extensions/forms'

type MyDataSet = {
firstName?: string
}

const data: MyDataSet = {
firstName: 'Nora',
}

// Method #1
function MyForm() {
return (
<Form.Handler
defaultData={data}
onSubmit={(data) => {
console.log(data.firstName)
}}
/>
)
}

// Method #2
const submitHandler = (data: MyDataSet) => {
console.log(data.firstName)
}
function MyForm() {
return <Form.Handler defaultData={data} onSubmit={submitHandler} />
}

// Method #3
import type { OnSubmit } from '@dnb/eufemia/extensions/forms'
const submitHandler: OnSubmit<MyDataSet> = (data) => {
console.log(data.firstName)
}
function MyForm() {
return <Form.Handler defaultData={data} onSubmit={submitHandler} />
}
```

## 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,17 @@ export const CommitHandleRef = () => {
<Form.Isolation
commitHandleRef={commitHandleRef}
transformOnCommit={(isolatedData, handlerData) => {
const value =
isolatedData.newPerson.title.toLowerCase()
// Because of missing TypeScript support
const contactPersons = handlerData['contactPersons']
const newPerson = isolatedData['newPerson']

const value = newPerson.title.toLowerCase()
const transformedData = {
...handlerData,
contactPersons: [
...handlerData.contactPersons,
...contactPersons,
{
...isolatedData.newPerson,
...newPerson,
value,
},
],
Expand Down Expand Up @@ -151,14 +154,18 @@ export const TransformCommitData = () => {

<Form.Isolation
transformOnCommit={(isolatedData, handlerData) => {
// Because of missing TypeScript support
const contactPersons =
handlerData['contactPersons']
const newPerson = isolatedData['newPerson']

return {
...handlerData,
contactPersons: [
...handlerData.contactPersons,
...contactPersons,
{
...isolatedData.newPerson,
value:
isolatedData.newPerson.title.toLowerCase(),
...newPerson,
value: newPerson.title.toLowerCase(),
},
],
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,37 @@ export function MyForm(props) {
}
```

## TypeScript support

You can define the TypeScript type structure for your data like so:

```tsx
import { Form, Field } from '@dnb/eufemia/extensions/forms'

type IsolationData = {
persons: Array<{ name: string }>
newPerson: Array<{ name: string }>
}

function MyForm() {
return (
<Form.Isolation<IsolationData>
onCommit={(data) => {
data // <-- is of type IsolationData
}}
transformOnCommit={(isolatedData, handlerData) => {
return {
...handlerData,
persons: [...handlerData.persons, isolatedData.newPerson],
}
}}
>
...
</Form.Isolation>
)
}
```

## Commit the data to the form

You can either use the `Form.Isolation.CommitButton` or provide a custom ref handler you can use (call) when you want to commit the data to the `Form.Handler` context:
Expand Down Expand Up @@ -111,7 +142,7 @@ render(

## Clear data from isolated fields

You can clear the isolation by calling `Form.clearData` with the `id` of the form.
You can clear the isolation by calling `clearData`:

```jsx
import { Form, Field } from '@dnb/eufemia/extensions/forms'
Expand All @@ -120,9 +151,8 @@ function MyForm() {
return (
<Form.Handler>
<Form.Isolation
id="my-isolated-data"
onCommit={() => {
Form.clearData('my-isolated-data')
onCommit={(data, { clearData }) => {
clearData()
}}
>
<Field.String path="/isolated" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ render(

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 data like so:

```tsx
type Data = { foo: string }

const { data } = Form.useData<Data>()
```

### 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:
Expand Down Expand Up @@ -89,15 +99,6 @@ function Component() {

This is beneficial when you need to utilize the form data in other places within your application.

### TypeScript support

You can define the TypeScript type structure for your data like so:

```tsx
type Data = { foo: string }
const { data } = Form.useData<Data>('unique')
```

### Select a single value

```jsx
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const IsolatedData = () => {

function ExistingPersonDetails() {
const { data, getValue } = Form.useData()
const person = getValue(data.selectedPerson)?.data || {}
const person = getValue(data['selectedPerson'])?.data || {}

return (
<Flex.Stack>
Expand Down Expand Up @@ -172,14 +172,15 @@ export const IsolatedData = () => {

function PushContainerContent() {
const { data, update } = Form.useData()
const selectedPerson = data['selectedPerson'] // Because of missing TypeScript support

// Clear the PushContainer data when the selected person is "other",
// so the fields do not inherit existing data.
React.useLayoutEffect(() => {
if (data.selectedPerson === 'other') {
if (selectedPerson === 'other') {
update('/pushContainerItems/0', {})
}
}, [data.selectedPerson, update])
}, [selectedPerson, update])

return (
<Flex.Stack>
Expand Down
Loading

0 comments on commit 10b199b

Please sign in to comment.