Skip to content

AssociatesTable

Pure presentation of the company's members. It never fetches; the caller passes the resolved associates (via useBusinessUnit) and owns the row markup via renderItem. The signed-in user (from OrganizationProvider) is flagged to the renderer.

import { AssociatesTable } from "@evolve-storefront/feature-organization/associates-table";

Renders inside OrganizationProvider.

Preview

DefaultOpen in Storybook ↗

Usage

const { data: unit } = useBusinessUnit();
// No children + a renderItem = the standard table; the row is yours.
<AssociatesTable
associates={unit?.associates.items}
renderItem={(associate) => <AssociateRow associate={associate} actions={<RowActions associate={associate} />} />}
/>;

Props

PropTypeDescription
associates?BusinessUnitAssociate[]

The resolved associates. The caller owns the query; the view renders.

renderItem?AssociatesTableRowRenderer<BusinessUnitAssociate>

Renders each associate's row — required for the default composition (no children). Receives { isCurrentUser } as derived state, since a callback cannot read the provider's context itself. Ignored when children are supplied (pass it to AssociatesTableRows).

emptyAction?ReactNode

Shown in the empty state (e.g. an invite button).

children?ReactNode

Compose a bespoke layout, or omit for the standard table.

Any other props go to the root element, like on a plain HTML element.

Change the layout

Pass children to AssociatesTable to arrange its parts yourself. Every part is exported from @evolve-storefront/feature-organization/associates-table.

<AssociatesTable>
<table className="w-full">
<thead><tr><th>{t("member")}</th><th>{t("roles")}</th><th>{t("actions")}</th></tr></thead>
<AssociatesTableRows
associates={unit.associates.items}
renderItem={(associate) => <AssociateRow associate={associate} />}
/>
</table>
</AssociatesTable>;
AssociateRowOne row in the associates table.
PropTypeDescription
associateBusinessUnitAssociate
isCurrentUser?boolean

Marks this row as the signed-in user.

actions?ReactNode

Row actions (edit roles / remove) — composed by the caller.

Any other props go to the root element, like on a plain HTML element.

AssociatesTableRowsThe associates <tbody>.

The key goes on a Fragment, not on a wrapper. Every sibling list part wraps renderItem's output in <li key={…}> and gets the key for free; a <tbody> has no valid wrapper, so the key was simply dropped and React warned on every render. A keyed Fragment adds no DOM — which is the only option between <tbody> and the consumer's <tr>.

PropTypeDescription
associatesBusinessUnitAssociate[]

The associates to render. Kept an explicit prop so renderItem's type flows from an extended query's shape.

renderItemAssociatesTableRowRenderer<BusinessUnitAssociate>

Renders each associate's row. Required — the feature owns no row markup.

Any other props go to the root element, like on a plain HTML element.

Loading and empty states

Render these while the data loads or when there is nothing to show, so the page does not jump when it arrives.

EmptyAssociatesEmptyEmpty state for the associates table.
LoadingAssociatesTableSkeletonLoading state for AssociatesTable.

Examples

Empty
// An empty company renders the empty state, with your action in it.
<AssociatesTable
associates={unit?.associates.items}
renderItem={renderItem}
emptyAction={<Button onClick={() => setInviting(true)}>{t("invite")}</Button>}
/>;
Open in Storybook ↗
LoadingSkeleton and real view adjacent — the only cheap check that the placeholder mirrors the DOM it replaces.
const { data: unit, isPending } = useBusinessUnit();
// Gate on isPending: the query waits for the session rather than settling empty.
if (isPending) return <AssociatesTableSkeleton />;
<AssociatesTable associates={unit?.associates.items} renderItem={renderItem} />;
Open in Storybook ↗
Remove From A Row ActionA per-record destructive action from a row's menu.
// The menu item sets state; one confirmation sits outside the table.
<>
<AssociatesTable associates={associates} renderItem={(associate) => (
<AssociateRow associate={associate} actions={<ActionsMenu label={t("actions")}
actions={[{ id: "remove", label: t("remove"), destructive: true, onSelect: () => confirm(associate) }]} />} />
)} />
<RemoveAssociateDialog associate={target} open={open} onOpenChange={setOpen} />
</>;
Open in Storybook ↗