Skip to content

Commit

Permalink
Fix propper Wizard and defaultValue support
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Sep 25, 2024
1 parent 8b24e74 commit 897148d
Show file tree
Hide file tree
Showing 10 changed files with 422 additions and 113 deletions.
17 changes: 5 additions & 12 deletions packages/dnb-eufemia/src/extensions/forms/Iterate/Array/Array.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import IterateItemContext, {
import SummaryListContext from '../../Value/SummaryList/SummaryListContext'
import ValueBlockContext from '../../ValueBlock/ValueBlockContext'
import FieldBoundaryProvider from '../../DataContext/FieldBoundary/FieldBoundaryProvider'
import DataContext from '../../DataContext/Context'
import useDataValue from '../../hooks/useDataValue'
import { useArrayLimit, useSwitchContainerMode } from '../hooks'
import { getMessage } from '../../FieldBlock'
Expand Down Expand Up @@ -90,7 +89,6 @@ function ArrayComponent(props: Props) {
value: arrayValue,
limit,
error,
defaultValue,
withoutFlex,
emptyValue,
placeholder,
Expand All @@ -100,7 +98,11 @@ function ArrayComponent(props: Props) {
setChanged,
onChange,
children,
} = useFieldProps(preparedProps)
} = useFieldProps(preparedProps, {
// To ensure the defaultValue set on the Iterate.Array is set in the data context,
// and will not overwrite defaultValues set by fields inside the Iterate.Array.
updateContextDataInSync: true,
})

useMountEffect(() => {
// To ensure the validator is called when a new item is added
Expand Down Expand Up @@ -129,15 +131,6 @@ function ArrayComponent(props: Props) {

const omitFlex = withoutFlex ?? (summaryListContext || valueBlockContext)

// To support React.StrictMode, we inject the defaultValue into the data context this way.
// The routine inside useFieldProps where updateDataValueDataContext is called, does not support React.StrictMode
const { handlePathChange } = useContext(DataContext) || {}
useMountEffect(() => {
if (defaultValue) {
handlePathChange?.(path, defaultValue)
}
})

useEffect(() => {
// Update inside the useEffect, to support React.StrictMode
valueCountRef.current = arrayValue || []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,31 +562,133 @@ describe('Iterate.Array', () => {
expect(onChangeIterate).toHaveBeenLastCalledWith(['foo'])
})

it('should handle "defaultValue" with React.StrictMode', () => {
const onSubmit = jest.fn()
describe('defaultValue', () => {
it('should validate required fields', async () => {
const onSubmit = jest.fn()

render(
<React.StrictMode>
render(
<Form.Handler onSubmit={onSubmit}>
<Iterate.Array path="/myList" defaultValue={['']}>
<Field.String itemPath="/" />
<Iterate.Array path="/myList" defaultValue={[null]}>
<Field.String itemPath="/" required />
</Iterate.Array>
</Form.Handler>
</React.StrictMode>
)
)

const form = document.querySelector('form')
const input = document.querySelector('input')
const form = document.querySelector('form')
fireEvent.submit(form)

expect(input).toHaveValue('')
expect(onSubmit).toHaveLength(0)

fireEvent.submit(form)
await waitFor(() => {
expect(
document.querySelectorAll('.dnb-form-status')
).toHaveLength(1)
})
})

expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myList: [''] },
expect.anything()
)
it('should handle "defaultValue" (empty string) in React.StrictMode', () => {
const onSubmit = jest.fn()

render(
<React.StrictMode>
<Form.Handler onSubmit={onSubmit}>
<Iterate.Array path="/myList" defaultValue={['']}>
<Field.String itemPath="/" />
</Iterate.Array>
</Form.Handler>
</React.StrictMode>
)

const form = document.querySelector('form')
const input = document.querySelector('input')

expect(input).toHaveValue('')

fireEvent.submit(form)

expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myList: [''] },
expect.anything()
)
})

it('should handle "defaultValue" (with value) in React.StrictMode', () => {
const onSubmit = jest.fn()

render(
<React.StrictMode>
<Form.Handler onSubmit={onSubmit}>
<Iterate.Array path="/myList" defaultValue={['foo']}>
<Field.String itemPath="/" />
</Iterate.Array>
</Form.Handler>
</React.StrictMode>
)

const form = document.querySelector('form')
const input = document.querySelector('input')

expect(input).toHaveValue('foo')

fireEvent.submit(form)

expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myList: ['foo'] },
expect.anything()
)
})

it('should handle "defaultValue" (with null) in React.StrictMode', () => {
const onSubmit = jest.fn()

render(
<React.StrictMode>
<Form.Handler onSubmit={onSubmit}>
<Iterate.Array path="/myList" defaultValue={[null]}>
<Field.String itemPath="/" defaultValue="foo" />
</Iterate.Array>
</Form.Handler>
</React.StrictMode>
)

const form = document.querySelector('form')
const input = document.querySelector('input')

expect(input).toHaveValue('foo')

fireEvent.submit(form)

expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myList: ['foo'] },
expect.anything()
)
})

it('should set empty array in the data context', () => {
const onSubmit = jest.fn()

render(
<React.StrictMode>
<Form.Handler onSubmit={onSubmit}>
<Iterate.Array path="/myList" defaultValue={[]}>
content
</Iterate.Array>
</Form.Handler>
</React.StrictMode>
)

const form = document.querySelector('form')
fireEvent.submit(form)

expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{ myList: [] },
expect.anything()
)
})
})

describe('with primitive elements', () => {
Expand Down Expand Up @@ -1294,7 +1396,7 @@ describe('Iterate.Array', () => {
<Form.Handler
onSubmit={onSubmit}
data={{
myList: ['', undefined, null, 'something'],
myList: [undefined, null, 'something'],
}}
>
<Iterate.Array path="/myList">
Expand All @@ -1311,26 +1413,20 @@ describe('Iterate.Array', () => {
)

const form = document.querySelector('form')
const [first, second, third, forth] = Array.from(
const [first, second, third] = Array.from(
document.querySelectorAll('input')
)

expect(first).toHaveValue('default value 1')
expect(second).toHaveValue('default value 2')
expect(third).toHaveValue('default value 3')
expect(forth).toHaveValue('default value 4')
expect(third).toHaveValue('something')

fireEvent.submit(form)

expect(onSubmit).toHaveBeenCalledTimes(1)
expect(onSubmit).toHaveBeenLastCalledWith(
{
myList: [
'default value 1',
'default value 2',
'default value 3',
'default value 4',
],
myList: ['default value 1', 'default value 2', 'something'],
},
expect.anything()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import Isolation from '../../Form/Isolation'
import PushContainerContext from './PushContainerContext'
import IterateItemContext from '../IterateItemContext'
import DataContext from '../../DataContext/Context'
import { clearedData } from '../../DataContext/Provider'
import useDataValue from '../../hooks/useDataValue'
import EditContainer, { CancelButton, DoneButton } from '../EditContainer'
import IterateArray, { ContainerMode } from '../Array'
Expand Down Expand Up @@ -43,6 +42,11 @@ export type Props = {
*/
data?: unknown | Record<string, unknown>

/**
* Prefilled data to add to the fields.
*/
defaultData?: unknown | Record<string, unknown>

/**
* A custom toolbar to be shown below the container.
*/
Expand All @@ -58,7 +62,8 @@ export type AllProps = Props & SpacingProps & ArrayItemAreaProps

function PushContainer(props: AllProps) {
const {
data,
data: dataProp,
defaultData: defaultDataProp,
path,
title,
children,
Expand Down Expand Up @@ -92,14 +97,24 @@ function PushContainer(props: AllProps) {
}

const { getValueByPath } = useDataValue()
const getFallback = useCallback(() => {
return Array.isArray(getValueByPath(path)) ? null : {}
}, [getValueByPath, path])

const data = useMemo(() => {
if (defaultDataProp) {
return // don't return a fallback, because we want to use the defaultData
}
return { newItems: [dataProp ?? getFallback()] }
}, [dataProp, defaultDataProp, getFallback])

const defaultData = useMemo(() => {
const value =
data ?? (Array.isArray(getValueByPath(path)) ? null : clearedData)
return { newItems: [value] }
}, [data, getValueByPath, path])
return { newItems: [defaultDataProp ?? getFallback()] }
}, [defaultDataProp, getFallback])

return (
<Isolation
data={data}
defaultData={defaultData}
emptyData={defaultData}
commitHandleRef={commitHandleRef}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ export const PushContainerProperties: PropertiesTableProps = {
type: 'React.Node',
status: 'optional',
},
data: {
doc: 'Prefilled data to be used by fields. Use `defaultData` when possible.',
type: ['object', 'array'],
status: 'optional',
},
defaultData: {
doc: 'Prefilled data to be used by fields.',
type: ['object', 'array'],
status: 'optional',
},
openButton: {
doc: 'The button to open container.',
type: 'React.Node',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ describe('PushContainer', () => {

render(
<Form.Handler onChange={onChange}>
<Iterate.PushContainer path="/">
<Iterate.PushContainer path="/" defaultData={[null]}>
<Field.String itemPath="/" defaultValue="foo" />
</Iterate.PushContainer>
</Form.Handler>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useCallback } from 'react'
import { Field, Form, Iterate, Value } from '../..'
import { Field, Form, Iterate, Tools, Value, Wizard } from '../..'
import { Card, Flex, Section } from '../../../../components'

export default {
Expand Down Expand Up @@ -256,3 +256,42 @@ export const WithArrayValidator = () => {
</Form.Handler>
)
}

export function InWizard() {
return (
<React.StrictMode>
<Form.Handler>
<Wizard.Container>
<Wizard.Step>
<Field.String
label="Regular"
path="/regular"
defaultValue="123"
/>
<Iterate.Array path="/items" defaultValue={[{}]}>
<Field.String
label="In Iterate"
itemPath="/x"
defaultValue="123"
/>
</Iterate.Array>

<Wizard.Buttons />
</Wizard.Step>

<Wizard.Step>
<Wizard.Buttons />
</Wizard.Step>
</Wizard.Container>

<Tools.Log />
</Form.Handler>

<Form.Handler>
<Iterate.Array path="/myList" defaultValue={[null]}>
<Field.String itemPath="/" defaultValue="bar" />
</Iterate.Array>
</Form.Handler>
</React.StrictMode>
)
}
2 changes: 1 addition & 1 deletion packages/dnb-eufemia/src/extensions/forms/Tools/Log.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext } from 'react'
import React, { useContext } from 'react'
import DataContext from '../DataContext/Context'
import Section, { SectionProps } from '../../../components/Section'

Expand Down
Loading

0 comments on commit 897148d

Please sign in to comment.