Skip to content

AddressBook

A searchable, paged address book.

import { AddressBook } from "@evolve-storefront/commerce-ui/address-book";

Renders inside AddressConfigProvider.

Design notes

For the case a picker cannot serve: a book with more addresses than a page. A radio list is right for the two or three a private shopper keeps and wrong for the sixty-six a company keeps — at that size the list is not a choice, it is a haystack, and the only usable control is a search box over a server query.

So searching and paging are the caller's round trips and this component filters nothing. That is not indirection: the set is too large to hold client-side, which is the entire reason this exists rather than the picker.

Rows are buttons rather than radios. A radio group's whole value is comparing the options at a glance, which is exactly what stops being possible here — and a picked address usually closes the surface this is in, so there is no group left to compare.

Preview

DefaultOpen in Storybook ↗

Usage

Search and paging wired to a stand-in query.

const { data, isPending } = useCompanyAddresses({ searchTerm, page });
<AddressBook
addresses={data?.items ?? []}
pagination={data?.pagination}
searchTerm={searchTerm}
onSearchTermChange={setSearchTerm}
onPageChange={setPage}
onSelect={pick}
isPending={isPending}
/>

Props

PropTypeDescription
addressesreadonly AddressBookEntry[]

The addresses on this page. The caller owns the query, so searching and paging are its round trips, not this component's filtering.

onPageChange(page: number) => void
onSearchTermChange(searchTerm: string) => void
onSelect(id: string) => void

An address was picked.

pagination?Pagination | null

Where this page sits in the full set. Omit while the first query is still in flight; the pager renders nothing when there is nowhere to go.

searchTerm?string

The term the caller is querying with. Controlled, because the query owns it.

selectedId?string

The picked one, where the caller knows which. Marks it, nothing more.

isPending?boolean

A query is in flight: the list dims and the rows stop responding.

addAction?ReactNode

An action beside the count — "Add address", the app's own. A slot rather than a prop because writing the address book is an account mutation, and this package cannot know whose.

emptyState?ReactNode

Shown instead of the list when the search matched nothing.

searchThreshold?number

Hide the search field while the whole book holds fewer addresses than this and no search is running: two addresses need no search. Omit to always show it.

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.

LoadingAddressBookSkeletonPlaceholder rows at the real card's height, so nothing reflows on the first page.