ArrayInput

ArrayInput

The <ArrayInput> component creates a list of sub-forms to edit arrays of data embedded inside a record. It works with react-hook-form's useFieldArray and integrates seamlessly with better-query.

Usage

<ArrayInput> allows editing of embedded arrays, like the items field in an order record:

import { 
  Edit,
  SimpleForm,
  TextInput,
  ArrayInput,
  NumberInput,
  SimpleFormIterator
} from '@/components/admin';

const OrderEdit = () => (
  <Edit>
    <SimpleForm>
      <TextInput source="customer" />
      <TextInput source="date" type="date" />
      <ArrayInput source="items">
        <SimpleFormIterator inline>
          <TextInput source="name" />
          <NumberInput source="price" />
          <NumberInput source="quantity" />
        </SimpleFormIterator>
      </ArrayInput>
    </SimpleForm>
  </Edit>
);

Installation

Terminal
npx better-admin add array-input

This will also install the required dependencies including simple-form-iterator.

Props

PropRequiredTypeDefaultDescription
sourceRequiredstring-Array field name
childrenRequiredReactNode-Iterator component (usually SimpleFormIterator)
classNameOptionalstring-CSS classes
defaultValueOptionalany[][]Initial array value
helperTextOptionalReactNode-Help text below the input
labelOptionalstringInferredLabel text
validateOptionalValidator | Validator[]-Array-level validation

SimpleFormIterator

The <SimpleFormIterator> component is the most common child of <ArrayInput>. It displays array items with controls for adding, removing, and reordering.

Props

PropRequiredTypeDefaultDescription
addButtonOptionalReactElementDefaultCustom add button
classNameOptionalstring-Wrapper classes
disableAddOptionalbooleanfalseHide add button
disableClearOptionalbooleanfalseHide clear-all button
disableRemoveOptionalboolean | (record)=>booleanfalseDisable remove per item
disableReorderingOptionalbooleanfalseHide move up/down buttons
getItemLabelOptionalboolean | (index)=>string|ReactElementfalsePer-item label
inlineOptionalbooleanfalseArrange inputs horizontally
removeButtonOptionalReactElementDefaultCustom remove button
reOrderButtonsOptionalReactElementDefaultCustom reorder buttons

With better-query

Use ArrayInput with better-query's nested data structures:

import { useQuery } from "better-admin";
import { Edit, SimpleForm, ArrayInput, SimpleFormIterator, TextInput, NumberInput } from '@/components/admin';

export function ProductEdit({ id }: { id: string }) {
  const { data, update } = useQuery("product", query);

  return (
    <Edit
      resource="product"
      id={id}
      onSubmit={(values) => {
        update.mutate({
          where: { id },
          data: values,
        });
      }}
    >
      <SimpleForm>
        <TextInput source="name" />
        <TextInput source="description" />
        <ArrayInput source="variants">
          <SimpleFormIterator>
            <TextInput source="name" label="Variant Name" />
            <NumberInput source="price" />
            <NumberInput source="stock" />
          </SimpleFormIterator>
        </ArrayInput>
      </SimpleForm>
    </Edit>
  );
}

Inline Layout

Use the inline prop to display inputs horizontally:

<ArrayInput source="items">
  <SimpleFormIterator inline>
    <TextInput source="name" />
    <NumberInput source="quantity" />
    <NumberInput source="price" />
  </SimpleFormIterator>
</ArrayInput>

Custom Labels

Add labels to each array item:

<ArrayInput source="addresses">
  <SimpleFormIterator getItemLabel={(index) => `Address #${index + 1}`}>
    <TextInput source="street" />
    <TextInput source="city" />
    <TextInput source="zipCode" />
  </SimpleFormIterator>
</ArrayInput>

Validation

Add validation at the array level or field level:

import { required, minLength } from '@/lib/validators';

<ArrayInput 
  source="items"
  validate={[required(), minLength(1, 'At least one item is required')]}
>
  <SimpleFormIterator>
    <TextInput source="name" validate={required()} />
    <NumberInput source="quantity" validate={[required(), min(1)]} />
  </SimpleFormIterator>
</ArrayInput>

Disable Input

To disable the entire array input, set the disabled prop on SimpleFormIterator and make child inputs readonly:

<ArrayInput source="items">
  <SimpleFormIterator disabled>
    <TextInput source="name" readOnly />
    <NumberInput source="price" readOnly />
  </SimpleFormIterator>
</ArrayInput>

Custom Buttons

Customize the add, remove, and reorder buttons:

import { Button } from '@/components/ui/button';
import { Plus, Trash } from 'lucide-react';

<ArrayInput source="items">
  <SimpleFormIterator
    addButton={
      <Button type="button" variant="outline">
        <Plus className="w-4 h-4 mr-2" />
        Add Item
      </Button>
    }
    removeButton={
      <Button type="button" variant="destructive" size="sm">
        <Trash className="w-4 h-4" />
      </Button>
    }
  >
    <TextInput source="name" />
    <NumberInput source="price" />
  </SimpleFormIterator>
</ArrayInput>

Complete Example

import {
  Edit,
  SimpleForm,
  TextInput,
  NumberInput,
  ArrayInput,
  SimpleFormIterator,
  SelectInput,
} from '@/components/admin';
import { useQuery } from 'better-admin';
import { query } from '@/lib/query';

export function OrderEdit({ id }: { id: string }) {
  const { data, update } = useQuery("order", query);

  return (
    <Edit
      resource="order"
      id={id}
      onSubmit={(values) => {
        update.mutate({
          where: { id },
          data: values,
        });
      }}
    >
      <SimpleForm>
        <TextInput source="customer" />
        <TextInput source="date" type="date" />
        <SelectInput
          source="status"
          choices={[
            { id: 'pending', name: 'Pending' },
            { id: 'processing', name: 'Processing' },
            { id: 'completed', name: 'Completed' },
          ]}
        />
        <ArrayInput source="items">
          <SimpleFormIterator
            inline
            getItemLabel={(index) => `Item #${index + 1}`}
          >
            <TextInput source="name" />
            <NumberInput source="quantity" min={1} />
            <NumberInput source="price" step={0.01} />
          </SimpleFormIterator>
        </ArrayInput>
      </SimpleForm>
    </Edit>
  );
}

Next Steps

On this page