Repeater
Persist collection changes inline while composing existing Repeater layouts and field editors.
InlineRepeater builds on the UI Repeater. It persists
add, remove, and move operations through a mutation while each child inline input
can save its own field independently.
Try the contact example in the playground. Its creation form uses Form Dialog directly beside the repeater. The form, validation, and mutations stay in one component, with no command registration.
Click Add contact, enter a required name, and submit Toevoegen. A row appears
only after creation succeeds. Cancel discards the form; failed creation keeps its
values for retry. Saved names update on blur. Enter error to simulate a failed save.
Use InlineRepeaterAdd for immediate collection additions. Use a form-dialog trigger
when creating an item requires validated input first.
Usage
import {
InlineRepeater,
InlineRepeaterAdd,
InlineRepeaterError,
InlineRepeaterRemove,
} from "@tulip-systems/inline/client";
import { RepeaterItem, RepeaterItems } from "@tulip-systems/ui/repeater";
<InlineRepeater
data={contacts}
getItemKey={(contact) => contact.id}
variables={(change) => ({ projectId, change })}
mutation={updateContactsMutation}
>
<RepeaterItems>
{contacts.map((contact) => (
<RepeaterItem key={contact.id} value={contact.id}>
<ContactInlineFields contact={contact} />
<InlineRepeaterRemove>Remove contact</InlineRepeaterRemove>
</RepeaterItem>
))}
</RepeaterItems>
<InlineRepeaterAdd>Add contact</InlineRepeaterAdd>
<InlineRepeaterError />
</InlineRepeater>ContactInlineFields is your component containing InlineField and inline inputs.
Update your supplied data through mutation callbacks, using local state or query
cache updates. The repeater does not assume a query key, cache shape, or server API.
Collection operations
The variables mapper receives InlineRepeaterChange<TItem>:
| Type | Additional fields | Purpose |
|---|---|---|
add | None | Create initial values in the mapper or on the server. |
remove | item, key, index | Remove the identified item. |
move | item, key, fromIndex, toIndex | Move an item to its final index. |
Your mutation can switch on the operation to call separate create, delete, and move
endpoints. All standard mutation lifecycle callbacks are preserved, including their
context and asynchronous completion. InlineRepeaterMove accepts direction="up"
or direction="down" and supports the same icon composition as RepeaterMove.
Saving and errors
Collection actions are disabled during mutation execution and callback completion,
including rapid repeated clicks. The wrapper retains supplied data on failure,
shows the error via InlineRepeaterError, and allows the action to be retried.
The error clears when a new action begins. If your callbacks optimistically change
the query cache, implement their rollback yourself.
useInlineRepeater() exposes status (idle, pending, success, or error),
isPending, and error for custom feedback. Collection state is separate from
InlineFieldError; child field mutations do not change the collection status.
Permissions and composition
The root accepts the usual permission and disabled settings and respects its
parent InlineProvider. Read-only, denied, disabled, and pending collection states
also disable descendant inline inputs through a nested provider. Ordinary UI inputs
still need their own disabled props.
Reuse RepeaterHeader, RepeaterItems, RepeaterItem, RepeaterItemHeader, and
RepeaterItemContent directly. InlineRepeaterAdd, InlineRepeaterRemove, and
InlineRepeaterMove share the base controls' asChild, button styling, and accessible
label behavior. Place them inside the inline root (and an item for remove/move).
Creation forms, validation of new items, drag-and-drop, and optimistic cache policies remain application composition choices. Existing child saves may already be in flight when a collection action starts; coordinate related backend operations in your application when deleting an item must wait for its field saves.