Core
Lists
Lists and conditionals work as they do in React: .map with a key, && and ternaries. This page covers the mistakes to avoid and the few habits that keep a long list fast.
A to-do list where each row has a key, so weft knows which row is which.
Today
import { state } from '@weft-js/core'import { Button, Card, Checkbox } from '@weft-js/warp'const start = ['Buy beans', 'Grind', 'Brew', 'Drink'].map(id => ({ id }))export default function Tasks() { const tasks = state(start) const later = () => { const [first, ...rest] = tasks.value tasks.value = [...rest, first!] } return ( <Card title="Today"> {tasks.value.map(t => <Checkbox key={t.id}>{t.id}</Checkbox>)} <Button emphasis="ghost" tone="neutral" onClick={later}> Do the first one later </Button> </Card> )}- Try this
- Tick Grind, then press Do the first one later a few times. The tick stays with its row.
- Why it matters
- Rows are moved, not rebuilt, so what is inside a row, such as a tick or typed text, survives a reorder.
xs.map(x => <Row key={x.id} … />)in JSX is a keyed list. Each key keeps its row; a new object under the same key updates the row in place.- Over objects, a missing
keyis W010 (autofix tokey={x.id}when items haveid). Over primitives, the value is the key. xs.filter(…).sort(…).map(…)works; put the chain in aderivedwhen other code needs it too.- No early
returninside the.mapcallback (W013). Filter first. cond && <X/>with a number on the left renders0(W011). Writen > 0 &&.- Stable items (W014).
xs.value = xs.value.map(p => ({ ...p, price }))gives every item a new identity, so every row re-runs on every change (1 DOM write, hundreds of re-runs). Replace only what changed:
``ts products.value = products.value.map(p => (p.id === id ? { ...p, price } : p)) ``
The compiler sees this inside one file; weft check follows the box through props into other files.
- One entry per row (W017). A row that reads
cart[p.id](a prop) orcart.value[p.id]depends on the wholecart, so adding one item re-runs all 150 rows. Read the entry from the box:cart.get(p.id)re-runs only the row whose entry changed (Object.is). For a prop, pass the box (cart={cart}, typedState<Cart>); the box form has an autofix. The same holds one step further: passinglive={live.value[s.id]}to a row component, or a row component readinglive.value[stock.id](in JSX orderived), re-runs every row. Pass the box and readlive.get(stock.id)in the row.