ShoppingListForm
Shared create/edit form root, built on the evolve-feature-form pattern:
a RAC Form whose validationErrors drive display, validated by one exported
Zod schema. Clean until the first submit, then live — and the submit button is
never disabled. It seeds ShoppingListFormContext so the composed
Fields/Submit pieces read the extras without prop-drilling.
import { ShoppingListForm } from "@evolve-storefront/feature-shopping-list/shopping-list-form";Renders inside ShoppingListProvider.
Preview
Usage
Empty create form — nothing shows until the first submit.
const t = useTranslations("ShoppingList.form.create");
// The dialog owns the footer; the form owns state and the <form>.<ShoppingListForm onValidSubmit={handleValid} isPending={create.isPending}> <ShoppingListFormFields /> <DialogFooter> <ShoppingListFormSubmit pendingLabel={t("pending")}> {t("submit")} </ShoppingListFormSubmit> </DialogFooter></ShoppingListForm>;Props
| Prop | Type | Description |
|---|---|---|
onValidSubmit | (values: { name: string; description?: string | undefined; }) => void | Runs with the validated values on a valid submit — where the mutation goes. |
children | ReactNode | The composed pieces — typically |
validationBehavior? | "aria" | "native" | Default |
defaultValues? | Partial<{ name: string; description?: string | undefined; }> | |
isPending? | boolean | |
error? | string | null | Form-level error (e.g. a failed mutation), shown above the actions. |
serverErrors? | Partial<Record<"name" | "description", string | string[]>> | Server-provided field errors, merged into the same display path. |
Any other props go to the root element, like on a plain HTML element.
Change the layout
Pass children to ShoppingListForm to arrange its parts yourself. Every part is exported from @evolve-storefront/feature-shopping-list/shopping-list-form.
const t = useTranslations("ShoppingList.form.create");
// The dialog owns the footer; the form owns state and the <form>.<ShoppingListForm onValidSubmit={handleValid} isPending={create.isPending}> <ShoppingListFormFields /> <DialogFooter> <ShoppingListFormSubmit pendingLabel={t("pending")}> {t("submit")} </ShoppingListFormSubmit> </DialogFooter></ShoppingListForm>;ShoppingListFormContextThe context ShoppingListForm fills.
No props of its own.
ShoppingListFormFieldsThe name + optional description fields, seeded from ShoppingListForm's context.
No props of its own.
ShoppingListFormSubmitThe form's submit button — never disabled (per the evolve-feature-form pattern: submission is gated by validity in the form's onSubmit, not by a disabled control).
| Prop | Type | Description |
|---|---|---|
pendingLabel | ReactNode | Shown while a submit is in flight (create/edit provide their own copy). |
isPending? | boolean | The action is running. The button keeps its label, so its width and
accessible name hold, shows a spinner and is |
variant? | ButtonVariant | |
tone? | ButtonTone | |
isIconOnly? | boolean | The button has no visible label, so it is square. Structural rather than a size: it says the button has nothing to be wide for, and says nothing about how big — the brand's control tokens still decide that. MoreNot inferred from the children. A kebab menu is as often a glyph or an SVG
as an |
asChild? | boolean | |
data-component? | undefined | A button's identity is not a caller's to rename — see |
Any other props go to the root element, like on a plain HTML element.
Examples
EditEdit mode: seeded with the list's current name and description.
const t = useTranslations("ShoppingList.form.edit");
<ShoppingListForm defaultValues={{ name: list.name, description: list.description ?? "" }} onValidSubmit={handleValid} isPending={update.isPending}> <ShoppingListFormFields /> <DialogFooter> <ShoppingListFormSubmit pendingLabel={t("pending")}> {t("submit")} </ShoppingListFormSubmit> </DialogFooter></ShoppingListForm>;PendingSubmitting: the button shows the pending label (never disabled).
// Straight off the mutation: the submit swaps to its pending label.<ShoppingListForm onValidSubmit={handleValid} isPending={create.isPending}> <ShoppingListFormFields /> <ShoppingListFormSubmit pendingLabel={t("pending")}> {t("submit")} </ShoppingListFormSubmit></ShoppingListForm>;With ErrorA failed mutation surfaces a form-level error above the actions.
// A form-level message for a failed mutation, shown above the actions.<ShoppingListForm onValidSubmit={handleValid} error={create.isError ? t("error") : null}> <ShoppingListFormFields /> <ShoppingListFormSubmit pendingLabel={t("pending")}> {t("submit")} </ShoppingListFormSubmit></ShoppingListForm>;
