Skip to content

Demos

Primitive items as fields

Code Editor
<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
Code Editor
<Value.SummaryList>
  <Iterate.Array
    defaultValue={['Iron Man', 'Captain America', 'The Hulk']}
  >
    <Value.String itemPath="/" />
  </Iterate.Array>
</Value.SummaryList>

Object items

Code Editor
<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.

Code Editor
<Iterate.Array
  defaultValue={['foo', 'bar']}
  onChange={(value) => console.log('onChange', value)}
>
  {(elementValue) => <Field.String value={elementValue} />}
</Iterate.Array>

Render properties with object items

Code Editor
<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.

Code Editor
<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

Code Editor
<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.

Title 1


Code Editor
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

Accounts

Account holder 1

Fornavn
Tony
Etternavn
Rogers

Code Editor
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

Persons

Tony with long name that maybe will wrap over to a new lineTTony with long name that maybe will wrap over to a new line
MariaMMaria

Using a line divider

Accounts

FornavnTony
EtternavnLast

FornavnMaria
EtternavnLast

Initially open


{
  "myList": [
    {}
  ]
} 
Code Editor
<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.

New account holder


Code Editor
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.

Code Editor
<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.

Remove me to see the minItems error.

{
  "myList": [
    {
      "foo": "Remove me to see the minItems error."
    }
  ]
} 

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.

Statsborgerskap

{
  "countries": [
    null
  ]
} 
Code Editor
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

Avengers

Iron Man


Captain America


Code Editor
<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

NameAge
EtternavnIron Man
EtternavnCaptain America
EtternavnThe Hulk
Code Editor
<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 1value 2
Code Editor
<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')
}
}
Code Editor
<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

{
  "outer": [
    {
      "inner": [
        "foo",
        "bar"
      ]
    }
  ]
} 
Code Editor
<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.

New person

Citizenship's


"undefined"