Skip to content

AddressBook

Pure presentation of the saved-address list. It never fetches; the caller passes the addresses it owns and composes each card via renderItem — wiring the connected actions (AddressDialog, DeleteAddressButton, SetDefaultAddressButton) itself.

import { AddressBook } from "@evolve-storefront/feature-account/address-book";

Renders inside AccountProvider.

Preview

DefaultOpen in Storybook ↗

Usage

const { data: book } = useCustomerAddresses();
<AddressBook
addresses={book?.items}
renderItem={(address) => (
<AddressCard>
<AddressLines lines={formatAddressLines(address, { locale })} />
</AddressCard>
)}
/>;

Props

PropTypeDescription
addresses?Address[]

The resolved addresses. The caller owns the query; the view only renders.

renderItem?AddressBookItemRenderer<Address>

Renders each address — required for the default composition (no children). Ignored when children are supplied (pass it to AddressBookItems).

empty?ReactNode

Custom empty state for the default composition.

emptyAction?ReactNode

Shown in the default empty state (e.g. an add-address button).

defaultAddress?Address | null

Pinned above the rest; see AddressBookItems.

searchTerm?string

The term the caller queries with. Controlled, because the query owns it: the book is paged and searched on the server, so this filters nothing itself.

onSearchTermChange?((searchTerm: string) => void)

Pass to show the search field.

headingLevel?HeadingLevel

Level for the group headings. Defaults to 3.

children?ReactNode

The composed pieces. Omit for the standard composition, which needs renderItem; pass children to compose a bespoke layout.

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

Change the layout

Pass children to AddressBook to arrange its parts yourself. Every part is exported from @evolve-storefront/feature-account/address-book.

AddressBookItemsThe saved addresses in one column, the default first and the rest under letter headings the way a contact list files them.

It renders the page it is handed and owns no pager: which page is showing is the caller's query state, and a book large enough to need one wants commerce-ui's AddressBook rather than a longer list.

PropTypeDescription
addressesAddress[]

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

renderItemAddressBookItemRenderer<Address>

Renders each address. Required — the feature owns no card markup.

empty?ReactNode

Custom empty state. Defaults to <AddressBookEmpty />.

defaultAddress?Address | null

The buyer's default delivery address, pinned above the rest under a heading of its own and left out of them. From the customer rather than the page, so it stays on top whichever page of a paged book is showing.

headingLevel?HeadingLevel

Level for the group headings. Defaults to 3.

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.

EmptyAddressBookEmptyEmpty state for the address book.
LoadingAddressBookSkeletonLoading state for AddressBook.

Examples

ComposedThe parts as children: the list and its empty state, composed by the caller.
<AddressBook>
<AddressBookItems
addresses={book.items}
renderItem={renderItem}
empty={<AddressBookEmpty>{addAddressButton}</AddressBookEmpty>}
/>
</AddressBook>;
Open in Storybook ↗
Default Pinned With SearchThe full account composition: the default first, under a heading of its own, the rest filed under letters, and a search field above them.
const [searchTerm, setSearchTerm] = useState("");
const { data } = useCustomerAddresses({ searchTerm });
<AddressBook
addresses={data?.items ?? []}
defaultAddress={customer.defaultShippingAddress}
searchTerm={searchTerm}
onSearchTermChange={setSearchTerm}
renderItem={(address) => (
<AddressCard>
<AddressCardHeader>
<AddressCardTitle>{address.name ?? fullName(address)}</AddressCardTitle>
<Button onClick={() => setEditing(address)}>{t("edit")}</Button>
</AddressCardHeader>
<AddressLines lines={formatAddressLines(address, { locale, includeName: Boolean(address.name) })} />
<AddressCardFooter><DeleteAddressButton address={address} /></AddressCardFooter>
</AddressCard>
)}
/>;
Open in Storybook ↗
EmptyEmpty state with an add-address action.
<AddressBook
addresses={book.items}
renderItem={renderItem}
emptyAction={<Button onClick={() => setAdding(true)}>{t("add-address")}</Button>}
/>;
Open in Storybook ↗
LoadingSkeleton and real view adjacent — the only cheap check that the placeholder mirrors the DOM it replaces.
const { data: book, isPending } = useCustomerAddresses();
// Same section, grid and card shape as the book, so nothing reflows.
if (isPending) return <AddressBookSkeleton />;
Open in Storybook ↗