Tulip Logo IconTulip
Inline

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>:

TypeAdditional fieldsPurpose
addNoneCreate initial values in the mapper or on the server.
removeitem, key, indexRemove the identified item.
moveitem, key, fromIndex, toIndexMove 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.

On this page