Skip to content
ONIWC
iwc 0.1.0

State management: .state.iwc

Purpose: data that lives in the visitor's browser, not the database, such as a shopping cart, a recently viewed list or a dismissed banner. It is updated by state actions, shown with state.* bindings and STATE.* lists, and can be sent to the server with a form. When: for anything that should work before sign-in and changes on every click. For anything that must be trusted (prices, stock, orders), the server reads its own database: client state is only ever a request.

Structure

.state.iwc
{
  "name": "cart",
  "persist": "local",
  "initial": { "items": [] },
  "mutations": {
    "add": { "op": "upsert", "path": "items", "key": "id", "count": "quantity" },
    "remove": { "op": "remove", "path": "items", "key": "id" },
    "clear": { "op": "clear", "path": "items" }
  },
  "derived": {
    "count": { "sum": "items", "field": "quantity" },
    "lines": { "count": "items" },
    "total": { "sum": "items", "multiply": ["price", "quantity"] }
  }
}
PropertyTypeRequiredMeaning
nameidentifieryesHow bindings, actions and forms refer to the store (cart), not the file name
persistlocal | session | nonenolocal: kept across visits and tabs (tabs stay in sync); session: until the tab closes; none: until the page changes
initialobjectnoStarting value. Every mutation path must be declared here
mutations{name: mutation}noThe only ways the store can change
derived{name: computation}noValues computed from lists, always up to date

Mutations

opChanges pathvalue passed by the button
setreplaces itthe new value
pushappends to a listthe item
upsertadds an item, or updates the one with the same key. With count, increments that field instead (by the value's count field, or 1). A count taken to 0 or below removes the itemthe item (needs key)
removeremoves items whose key matchesthe key value, or an object with it
incrementadds by (default 1) to a numbernone
clearresets to initial (the whole store if path is omitted)none

upsert with a negative count is how a cart's "−" button works: {"id": "item.id", "quantity": -1}.

Derived values

FormComputes
{"count": "items"}number of entries
{"sum": "items", "field": "quantity"}sum of a field
{"sum": "items", "multiply": ["price", "quantity"]}sum of products (a subtotal)
{"sum": "values"}sum of a list of numbers

Using a store

Change it with a button or link (action: "state"). Values starting with item. or props. are filled from the current record when the page renders:

element (in a .iwc file)
{ "type": "button", "label": "Add to cart", "action": "state", "store": "cart", "mutation": "add", "value": { "id": "item.id", "slug": "item.slug", "name": "item.name", "price": "item.price", "image": "item.image", "quantity": 1 } }

Mutations whose name contains add, upsert or push show a short "Added" notice.

Show a value anywhere, updated live:

element (in a .iwc file)
{ "type": "text", "variant": "span", "bind": "state.cart.count", "className": "cart-count" }
element (in a .iwc file)
{ "type": "text", "bind": "state.cart.total", "format": "currency", "currency": "USD", "prefix": "Subtotal: " }

List it with a STATE.<store>.<list> source. See data binding → STATE lists. A full cart with +/− buttons: examples/shop/ui/cart.ui.iwc.

Send it with a form: "include": ["STATE.cart.items"] adds the list to the request body as items. On success, "mutation": "cart.clear" empties it. See forms.

Rules and limits

  • A server-rendered page can't read client state, so state.* values appear when the page's script runs (a fraction of a second). Pick neutral empty styling, e.g. .cart-count:empty::before { content: "0" }.
  • Derived values cover counts and sums only. There is no per-item computed value (a line total of price × quantity) and no conditional display based on state. See limitations.
  • The browser can change client state freely, so never trust it: the store's checkout action reads prices and stock from the database, and quantities are validated by the API.

Common mistakes

MessageFix
path "lines" is not declared in "initial"Add "lines": [] to initial
upsert mutations need a "key" field to identify itemsAdd "key": "id"
state store "crat" not foundstore and STATE. use the store's name
state store "cart" has no mutation "addd"Check mutations
name the list to repeat, e.g. STATE.cart.itemsA store is an object; a list source needs its path

Source: iwc/docs/state-management.md