Skip to content

Accumulate state.include instead of replacing it - #805

Merged
acasazza merged 1 commit into
v5.0.0from
fix/order-include-accumulation
Jul 29, 2026
Merged

Accumulate state.include instead of replacing it#805
acasazza merged 1 commit into
v5.0.0from
fix/order-include-accumulation

Conversation

@acasazza

Copy link
Copy Markdown
Member

state.include is the union of the resources every mounted component needs, but it was being replaced rather than accumulated — so components silently lost each other's includes.

Why this is worse than a missing field

Commerce Layer only embeds relationships you explicitly request, and the response cannot distinguish the two cases:

order.shipments[0].shipping_method === undefined  // "no method is selected"
order.shipments === undefined                     // "I didn't ask for shipments"

A dropped include therefore does not fail the request — it produces wrong application state. Losing shipments.shipping_method makes a selected shipping method look unselected.

Mechanism

setIncludesResource fell through to baseReducer, whose { ...state, ...payload } spread replaces include wholesale.

addResourceToInclude is a free function with no store access, so it can only union against the resourcesIncluded it is handed — and most of the 26 call sites don't pass it. Components that dispatch more than once from a single effect pass all read the same pre-update snapshot, so the last dispatch wins and the others' resources are gone.

PlaceOrderContainer does exactly this — three dispatches in one pass, only the third passing resourcesIncluded:

Block Dispatches Passes resourcesIncluded?
1 5 × shipments.* incl. shipments.shipping_method no
2 billing_address no
3 shipping_address yes

The effect then re-runs, still sees shipments absent, and repeats the same self-defeating dance.

Confirmed on the wire

OrderReducer.includeRequest.spec.ts builds state through that exact dispatch sequence and asserts what getApiOrder passes to sdk.orders.retrieve. Against the old reducer:

AssertionError: expected [ 'line_items.item', …(1) ] to include 'shipments.shipping_method'

The request was include: ['line_items.item', 'shipping_address']shipments.shipping_method and billing_address genuinely absent. Not inferred from reading the code.

Fix

Handle the action in orderReducer and union there, so all 26 call sites are correct whether or not they pass resourcesIncluded. Fixing it here rather than in each caller is why this belongs in react-components instead of being worked around downstream.

Two behaviours are preserved deliberately:

  • An explicitly empty list still resets — the effect teardown in useOrderState dispatches include: [] to clear the list, and a naive union would silently make that a no-op.
  • include is left untouched when there is nothing to merge — callers that only report includeLoaded omit it, and turning undefined into [] would flip the include?.length === 0 checks in that same hook and change its effect dependencies.

Tests

Both new specs were verified to fail against the old reducer before being accepted (3 of 6, and 1 of 2), so they are regression tests rather than tautologies.

Notes

  • Targets v5.0.0; main is the v4 line.
  • Independent of Close payment-gateway render loop and repair gift-card/coupon submit #804 — verified on this base with the full suite green.
  • pnpm typecheck reports 35 errors on this branch. They are byte-identical to the pre-existing baseline and unrelated to this change; clearing them is part of the separate dependency-upgrade work.

🤖 Generated with Claude Code

`state.include` is the union of the resources every mounted component
needs, but `setIncludesResource` fell through to `baseReducer`, whose
plain `{ ...state, ...payload }` spread replaced the list wholesale.

`addResourceToInclude` is a free function with no store access, so it can
only union against the `resourcesIncluded` it is handed — and most of the
26 call sites don't pass it. Components that dispatch more than once from
a single effect pass all read the same stale snapshot, so the last
dispatch dropped the others' resources. In `PlaceOrderContainer` that
lost five `shipments.*` resources plus `billing_address`, putting only
`["line_items.item", "shipping_address"]` on the wire.

This matters more than a missing field: the API cannot distinguish "I did
not request this relationship" from "this relationship is unset", so a
dropped `shipments.shipping_method` makes a selected shipping method look
unselected rather than failing the request.

Handle the action in `orderReducer` and union there, so every call site is
correct whether or not it passes `resourcesIncluded`. Two behaviours are
preserved deliberately: an explicitly empty list still resets, because the
effect teardown in `useOrderState` relies on it; and `include` is left
untouched when there is nothing to merge, since turning `undefined` into
`[]` would flip the `include?.length === 0` checks in that same hook.
@malessani malessani changed the title fix(order): accumulate state.include instead of replacing it Accumulate state.include instead of replacing it Jul 29, 2026
@acasazza
acasazza merged commit 5134f3a into v5.0.0 Jul 29, 2026
2 checks passed
@acasazza
acasazza deleted the fix/order-include-accumulation branch July 29, 2026 09:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants