Demos
Primitive items as fields
<Iterate.Array defaultValue={['Iron Man', 'Captain America', 'The Hulk']} onChange={console.log} > <Field.String itemPath="/" /> </Iterate.Array>
Primitive items as values
- Iron Man
- Captain America
- The Hulk
<Value.SummaryList> <Iterate.Array defaultValue={['Iron Man', 'Captain America', 'The Hulk']} > <Value.String itemPath="/" /> </Iterate.Array> </Value.SummaryList>
Object items
<Iterate.Array defaultValue={[ { accountName: 'Brukskonto', accountNumber: '90901134567', }, { accountName: 'Sparekonto', accountNumber: '90901156789', }, ]} onChange={(value) => console.log('onChange', value)} > <Field.Composition> <Field.BankAccountNumber itemPath="/accountNumber" /> <Field.String label="Account name" itemPath="/accountName" /> </Field.Composition> </Iterate.Array>
Render properties with primitive items
You can provide the child as a function that receives the value of the item as the first argument, and the index of which item you are on as the second.
<Iterate.Array defaultValue={['foo', 'bar']} onChange={(value) => console.log('onChange', value)} > {(elementValue) => <Field.String value={elementValue} />} </Iterate.Array>
Render properties with object items
<Iterate.Array defaultValue={[ { num: 1, txt: 'One', }, { num: 2, txt: 'Two', }, ]} onChange={(value) => console.log('onChange', value)} > {({ num, txt }) => ( <Field.Composition width="large"> <Field.Number value={num} width="small" /> <Field.String value={txt} width={false} /> </Field.Composition> )} </Iterate.Array>
Conditions using Visibility
The second field will be visible when the first has a value.
<Form.Handler> <Iterate.Array path="/myList" defaultValue={[{}]}> <Flex.Stack> <Field.Name.First className="firstName" itemPath="/firstName" /> <Form.Visibility animate visibleWhen={{ itemPath: '/firstName', hasValue: (value) => Boolean(value), }} > <Field.Name.Last className="lastName" itemPath="/lastName" /> </Form.Visibility> </Flex.Stack> </Iterate.Array> </Form.Handler>
Dynamic path value
<Form.Handler defaultData={{ count: 0, }} > <Flex.Stack> <Field.Number path="/count" width="small" showStepControls /> <Iterate.Array path="/items" countPath="/count" countPathTransform={({ value, index }) => { return 'myObject' in (value || {}) ? value : { myObject: index, } }} > <Field.Number itemPath="/myObject" label="Item no. {itemNo}" /> </Iterate.Array> </Flex.Stack> </Form.Handler>
Animated container
With an optional title and Iterate.Toolbar.
const MyForm = () => { const { count } = Iterate.useCount('myForm') return ( <Form.Handler defaultData={{ myList: ['Item 1'], }} id="myForm" > <Form.Card> <Iterate.Array path="/myList" placeholder={<>Empty list</>}> <Iterate.AnimatedContainer title="Title {itemNo}"> <Field.String label="Label" itemPath="/" /> <Iterate.Toolbar> <Iterate.RemoveButton /> </Iterate.Toolbar> </Iterate.AnimatedContainer> </Iterate.Array> <Iterate.PushButton path="/myList" pushValue={`Item ${String(count('/myList') + 1)}`} text="Add new item" /> </Form.Card> </Form.Handler> ) } render(<MyForm />)
Toggle between a view and edit container
const MyEditItemForm = () => { return ( <Field.Composition> <Field.Name.First itemPath="/firstName" width="medium" /> <Field.Name.Last itemPath="/lastName" width="medium" required /> </Field.Composition> ) } const MyEditItem = () => { return ( <Iterate.EditContainer title="Edit account holder {itemNo}" titleWhenNew="New account holder {itemNo}" > <MyEditItemForm /> </Iterate.EditContainer> ) } const MyViewItem = () => { const item = Iterate.useItem() console.log('index:', item.index) return ( <Iterate.ViewContainer title="Account holder {itemNo}"> <Value.SummaryList> <Value.Name.First itemPath="/firstName" showEmpty /> <Value.Name.Last itemPath="/lastName" placeholder="-" /> </Value.SummaryList> </Iterate.ViewContainer> ) } const CreateNewEntry = () => { return ( <Iterate.PushContainer path="/accounts" title="New account holder" openButton={ <Iterate.PushContainer.OpenButton text="Add another account" /> } showOpenButtonWhen={(list) => list.length > 0} > <MyEditItemForm /> </Iterate.PushContainer> ) } const MyForm = () => { return ( <Form.Handler data={{ accounts: [ { firstName: 'Tony', lastName: 'Rogers', }, ], }} onChange={(data) => console.log('DataContext/onChange', data)} onSubmit={async (data) => console.log('onSubmit', data)} > <Flex.Stack> <Form.MainHeading>Accounts</Form.MainHeading> <Form.Card gap={false}> <Iterate.Array path="/accounts"> <MyViewItem /> <MyEditItem /> </Iterate.Array> <CreateNewEntry /> </Form.Card> <Form.SubmitButton variant="send" /> </Flex.Stack> </Form.Handler> ) } render(<MyForm />)
Customize the view and edit containers
- Using
variant="filled"will render the Iterate.ViewContainer and Iterate.EditContainer with a background color. - Using
toolbarVariant="custom"will render the Iterate.Toolbar without any spacing so you can customize it to your needs.
Using a line divider
Initially open
<Form.Handler required> <Wizard.Container> <Wizard.Step> <Form.Card> <Iterate.Array path="/myList" defaultValue={[{}]}> <Iterate.ViewContainer> <Value.String label="Item {itemNo}" itemPath="/foo" /> </Iterate.ViewContainer> <Iterate.EditContainer> <Field.String label="Item {itemNo}" itemPath="/foo" defaultValue="foo" /> </Iterate.EditContainer> </Iterate.Array> <Iterate.PushButton text="Add" path="/myList" variant="tertiary" pushValue={{}} /> </Form.Card> <Wizard.Buttons /> </Wizard.Step> <Wizard.Step> <Iterate.Array path="/myList" defaultValue={[{}]}> <Iterate.EditContainer> <Field.String label="Item {itemNo}" itemPath="/foo" defaultValue="foo" /> </Iterate.EditContainer> <Iterate.ViewContainer> <Value.String label="Item {itemNo}" itemPath="/foo" /> </Iterate.ViewContainer> </Iterate.Array> <Wizard.Buttons /> </Wizard.Step> </Wizard.Container> <Tools.Log top /> </Form.Handler>
Required
With a Iterate.PushContainer to add a new item.
The new item gets inserted at the beginning of the array by using the reverse property.
const MyViewItem = () => { return ( <Iterate.ViewContainer title="Account holder {itemNo}"> <Value.SummaryList> <Value.Name.First itemPath="/firstName" /> <Value.Name.Last itemPath="/lastName" /> </Value.SummaryList> </Iterate.ViewContainer> ) } const MyEditItem = () => { return ( <Iterate.EditContainer title="Edit account holder {itemNo}" titleWhenNew="New account holder {itemNo}" > <MyEditItemContent /> </Iterate.EditContainer> ) } const MyEditItemContent = () => { return ( <Field.Composition width="large"> <Field.Name.First itemPath="/firstName" required /> <Field.Name.Last itemPath="/lastName" required /> </Field.Composition> ) } render( <Form.Handler> <Form.Card> <Iterate.PushContainer path="/myListOfPeople" title="New account holder" > <MyEditItemContent /> </Iterate.PushContainer> <Iterate.Array path="/myListOfPeople" reverse animate required errorMessages={{ 'Field.errorRequired': 'Custom message', }} > <MyViewItem /> <MyEditItem /> </Iterate.Array> </Form.Card> <Form.SubmitButton /> </Form.Handler>, )
With a Iterate.PushButton to add a new item.
<Form.Handler> <Form.Card> <Iterate.Array path="/items" animate required errorMessages={{ 'Field.errorRequired': 'Custom message', }} validateInitially > <Flex.Horizontal> <Field.String itemPath="/" /> <Iterate.RemoveButton /> </Flex.Horizontal> </Iterate.Array> <Iterate.PushButton path="/items" pushValue="baz" text="Add item to hide error" /> </Form.Card> <Form.SubmitButton /> </Form.Handler>
Minium one item
There are several ways to achieve this:
By using a schema
This example uses the minItems in a schema with a custom error message.
const schema = {type: 'object',properties: {myList: {type: 'array',minItems: 1,},},}
It will show the error message when the array is empty.
By using the toolbarVariant property
This example uses the container's toolbarVariant property with the value minimumOneItem.
It hides the toolbar in the EditContainer when there is only one item in the array. And it hides the remove button in the ViewContainer when there is only one item in the array.
const MyForm = () => { const { getCountryNameByIso } = Value.SelectCountry.useCountry() return ( <Form.Handler onSubmit={async (data) => console.log('onSubmit', data)} onSubmitRequest={() => console.log('onSubmitRequest')} > <Flex.Stack> <Form.MainHeading>Statsborgerskap</Form.MainHeading> <Form.Card> <Iterate.Array path="/countries" defaultValue={[null]} onChangeValidator={(arrayValue) => { const findFirstDuplication = (arr) => arr.findIndex((e, i) => arr.indexOf(e) !== i) const index = findFirstDuplication(arrayValue) if (index > -1) { return new Error( `You cannot have duplicate items: ${getCountryNameByIso( String(arrayValue.at(index)), )}`, ) } }} > <Iterate.ViewContainer toolbarVariant="minimumOneItem"> <Value.SelectCountry label="Land du er statsborger i" itemPath="/" /> </Iterate.ViewContainer> <Iterate.EditContainer toolbarVariant="minimumOneItem"> <Field.SelectCountry label="Land du er statsborger i" itemPath="/" required /> </Iterate.EditContainer> </Iterate.Array> <Iterate.PushButton path="/countries" pushValue={null} text="Legg til flere statsborgerskap" /> </Form.Card> <Form.SubmitButton variant="send" /> <Tools.Log /> </Flex.Stack> </Form.Handler> ) } render(<MyForm />)
With DataContext and add/remove buttons
<Form.Handler data={{ avengers: [ { nickname: 'Iron Man', firstName: 'Tony', lastName: 'Stark', }, { nickname: 'Captain America', firstName: 'Steve', lastName: 'Rogers', }, ], }} onChange={(data) => console.log('DataContext/onChange', data)} > <Flex.Stack> <Form.MainHeading>Avengers</Form.MainHeading> <Form.Card> <Iterate.Array path="/avengers" onChange={(value) => console.log('Iterate/onChange', value)} > <Iterate.AnimatedContainer title={ <Value.String label={false} itemPath="/nickname" placeholder="A Nick name" /> } > <Field.Name itemPath="/nickname" width="medium" label="Nick name" /> <Field.Composition> <Field.Name.First itemPath="/firstName" width="medium" /> <Field.Name.Last itemPath="/lastName" width="medium" /> </Field.Composition> <Iterate.Toolbar> <Iterate.RemoveButton showConfirmDialog /> </Iterate.Toolbar> </Iterate.AnimatedContainer> </Iterate.Array> <Iterate.PushButton text="Add another avenger" path="/avengers" pushValue={{}} /> </Form.Card> </Flex.Stack> </Form.Handler>
Static generated in a Table
| Name | Age |
|---|---|
| EtternavnIron Man | |
| EtternavnCaptain America | |
| EtternavnThe Hulk |
<Table> <thead> <Tr> <Th>Name</Th> <Th>Age</Th> </Tr> </thead> <tbody> <Iterate.Array withoutFlex defaultValue={[ { name: 'Iron Man', age: 45, }, { name: 'Captain America', age: 123, }, { name: 'The Hulk', age: 3337, }, ]} > <Tr> <Td> <Value.Name.Last itemPath="/name" /> </Td> <Td> <Value.Number itemPath="/age" /> </Td> </Tr> </Iterate.Array> </tbody> </Table>
Value composition
<Value.Composition> <Iterate.Array defaultValue={[ { value: 'value 1', }, { value: 'value 2', }, ]} > <Value.String itemPath="/value" /> </Iterate.Array> </Value.Composition>
Array validator
You can also add a validator to ensure that the array contains at least one item:
const validator = (arrayValue) => {if (!(arrayValue?.length > 0)) {return new Error('You need at least one item')}}
<Form.Handler defaultData={{ items: ['foo'], }} onSubmit={async () => console.log('onSubmit')} > <Form.Card> <Iterate.Array path="/items" onChangeValidator={(arrayValue) => { if (!(arrayValue && arrayValue.length > 1)) { return new Error('You need at least two items') } }} animate > <Flex.Horizontal align="flex-end"> <Field.String label="Item no. {itemNo}" itemPath="/" width="medium" size="medium" /> <Iterate.RemoveButton showConfirmDialog /> </Flex.Horizontal> </Iterate.Array> <Iterate.PushButton top path="/items" pushValue={null} text="Add" /> <Form.SubmitButton /> </Form.Card> </Form.Handler>
Nested Iterate
<Form.Handler data={{ outer: [ { inner: ['foo', 'bar'], }, ], }} > <Iterate.Array path="/outer"> <Iterate.Array itemPath="/inner"> <Field.String label="Item {itemNo}" itemPath="/" /> </Iterate.Array> </Iterate.Array> <Tools.Log /> </Form.Handler>
Nested Iterate with PushContainer
This demo uses the Iterate.PushContainer component to add new items to an nested array by using the itemPath property.