DynamicListField
DynamicListField manages an array of objects. Each item in the array is rendered as a row whose cells are defined by the slots prop. Supported slot types are text, number, longText, radio, object, autocomplete, counter, calculated, and template. Items can be added via a button, via an autocomplete picker, or both. Rows can optionally be reordered with up/down arrows.
Props
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| value | any[] | Yes | — | The current list of item objects. |
| onChange | (items: any[]) => void | Yes | — | Called with the updated array on any mutation. |
| slots | Slot[] | Yes | — | Column/cell definitions for each row (see Slot shape below). |
| label | string | No | — | Label rendered in the field header. |
| description | string | No | — | Helper text rendered below the field. |
| canAddItems | boolean | No | true | Whether to show the "Add item" button. |
| canRemoveItems | boolean | No | true | Whether to show the delete button on each row. |
| isSortable | boolean | No | false | Whether to show up/down arrow buttons on each row. |
| addItemMode | 'button' \| 'autocomplete' \| 'both' | No | — | Controls which add mechanisms are shown. When omitted, defaults to button if canAddItems is true. |
| addItemPickerConfig | AddItemPickerConfig | No | — | Configuration for the autocomplete picker (used when addItemMode is 'autocomplete' or 'both'). See shape below. |
| getDefaultItem | (ctx: { slots: Slot[]; items: any[] }) => Record<string, any> | No | — | Factory function that returns the initial value for a newly added item. Overrides the default per-slot initialization. |
| breakpoint | number | No | 800 | Viewport width (px) below which the row layout switches to column mode. |
| forceMobileView | boolean | No | false | Force the column layout regardless of viewport width. |
| itemStyle | React.CSSProperties | No | — | Styles applied to each row container. |
| containerStyle | React.CSSProperties | No | — | Styles applied to the outer FieldContainer. |
| headerStyle | React.CSSProperties | No | — | Styles applied to the field header area. |
| bodyStyle | React.CSSProperties | No | — | Styles applied to the field body area. |
| labelStyle | React.CSSProperties | No | — | Styles applied to the label element. |
| descriptionStyle | React.CSSProperties | No | — | Styles applied to the description paragraph. |
| className | string | No | — | CSS class name applied to the outer container. |
| id | string | No | — | id attribute forwarded. |
Slot shape
interface Slot {
type: 'text' | 'number' | 'longText' | 'radio' | 'object' | 'autocomplete' | 'counter' | 'calculated' | 'template';
name: string; // Key used to read/write the value on each item object
label?: string; // Optional column header label
config?: Record<string, any>; // Type-specific configuration (placeholder, options, min, max, etc.)
}
AddItemPickerConfig shape (key properties)
interface AddItemPickerConfig {
apiBaseUrl?: string;
path?: string;
searchParam?: string;
placeholder?: string;
mapOptionToItem?: (option: any) => Record<string, any>;
getOptionId?: (option: any) => string | number;
getItemId?: (item: any) => string | number;
allowDuplicates?: boolean;
maxItems?: number;
position?: 'top' | 'bottom';
disabled?: boolean | ((items: any[]) => boolean);
onItemAdded?: (item: any, option: any) => void;
// ... additional display and search options
}
Usage
Basic list with text and number slots
import React, { useState } from 'react';
import DynamicListField from '@/components/fields/DynamicListField';
const slots = [
{ type: 'text', name: 'description', label: 'Description', config: { placeholder: 'Enter description' } },
{ type: 'number', name: 'quantity', label: 'Qty', config: { min: 1 } },
{ type: 'number', name: 'price', label: 'Price', config: { step: 0.01 } },
];
export default function Example() {
const [items, setItems] = useState([]);
return (
<DynamicListField
label="Order lines"
slots={slots}
value={items}
onChange={setItems}
/>
);
}
With autocomplete picker and sort
<DynamicListField
label="Products"
slots={[
{ type: 'text', name: 'name', label: 'Name' },
{ type: 'counter', name: 'qty', label: 'Qty', config: { minValue: 1 } },
]}
value={lines}
onChange={setLines}
isSortable={true}
addItemMode="both"
addItemPickerConfig={{
apiBaseUrl: 'https://api.example.com',
path: '/v1/products',
searchParam: 'q',
mapOptionToItem: (opt) => ({ name: opt.name, qty: 1 }),
getOptionId: (opt) => opt.id,
getItemId: (item) => item.id,
}}
/>