Skip to content
weftGitHub

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>  )}
  1. State is a box. Read and write .value. Everywhere, JSX included (W015).
  2. Anything computed from other values is derived. Not in a watch or the component body (W001).
  3. JSX is always live. Every {expr} and attribute re-evaluates when what it reads changes.
  4. Props are always current. Destructure them (one level, W006). Read them in derived, watch, JSX or handlers. const x = props.y is a live read-only alias. let x = props.y and props.y = write locally; only bind:y={box} writes out.
  5. The component body runs once. No early returns on state (W040), no let counters (W041). Branch in JSX.
  6. Pass a box only to give write access (value={coupon}, child prop typed State<string | null>), or so list rows can read one entry: cart.get(p.id) re-runs one row, cart[p.id] or cart.value[p.id] re-runs them all (W017), in the list and in the row component alike.
  7. Lists and conditionals the way you know them: .map with key (W010), &&, ternaries. Keep list items stable: replace only the item that changed (p.id === id ? { ...p, price } : p), never ({ ...p }) every item (W014).
  8. Copy a prop into state with state.linked(() => prop), not state(prop) (W005). untrack(() => …) is a snapshot that never updates: not for anything you render (W018).
  9. Side effects go in watch(() => source, value => cleanup?); DOM work in onMount. flush() applies pending DOM updates now.