Core
The rules
Nine rules cover how every weft component behaves. The example below uses most of them. Nearly all carry a code, so when you break one, the build names it and says how to fix it.
A quantity, plus shipping and a total that are worked out from it. Shipping turns free at three items.
Your basket
1 × $12 · shipping $4
Total $16
import { derived, state } from '@weft-js/core'import { Button, Card } from '@weft-js/warp'export default function Basket() { const price = 12 const qty = state(1) const shipping = derived(() => (qty.value >= 3 ? 0 : 4)) const total = derived(() => price * qty.value + shipping.value) return ( <Card title="Your basket"> <p>{qty.value} × $12 · shipping ${shipping.value}</p> <p><strong>Total ${total.value}</strong></p> <Button onClick={() => qty.value++}>Add one</Button> <Button emphasis="ghost" tone="neutral" onClick={() => (qty.value = 1)} > Reset </Button> </Card> )}- Try this
- Press Add one twice. At three items shipping drops to $0 and the total changes with it.
- Why it matters
- You say what the total is once, and it can never go stale. Most bugs in forms and carts come from keeping such values in sync by hand.
tsx
import { derived } from '@weft-js/core'export function Cart({ items, taxRate }: { items: { id: string; name: string; price: number }[]; taxRate: number }) { const subtotal = derived(() => items.reduce((s, i) => s + i.price, 0)) const total = derived(() => subtotal.value * (1 + taxRate)) return ( <section> <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul> <p>{total.value.toFixed(2)}</p> </section> )}- State is a box. Read and write
.value. Everywhere, JSX included (W015). - Anything computed from other values is
derived. Not in awatchor the component body (W001). - JSX is always live. Every
{expr}and attribute re-evaluates when what it reads changes. - Props are always current. Destructure them (one level, W006). Read them in
derived,watch, JSX or handlers.const x = props.yis a live read-only alias.let x = props.yandprops.y =write locally; onlybind:y={box}writes out. - The component body runs once. No early returns on state (W040), no
letcounters (W041). Branch in JSX. - Pass a box only to give write access (
value={coupon}, child prop typedState<string | null>), or so list rows can read one entry:cart.get(p.id)re-runs one row,cart[p.id]orcart.value[p.id]re-runs them all (W017), in the list and in the row component alike. - Lists and conditionals the way you know them:
.mapwithkey(W010),&&, ternaries. Keep list items stable: replace only the item that changed (p.id === id ? { ...p, price } : p), never({ ...p })every item (W014). - Copy a prop into state with
state.linked(() => prop), notstate(prop)(W005).untrack(() => …)is a snapshot that never updates: not for anything you render (W018). - Side effects go in
watch(() => source, value => cleanup?); DOM work inonMount.flush()applies pending DOM updates now.