row-reads-a-plain-field
This fails the run. Where it is wrong about your code, // ramonda-check-ignore <reason> on the line says so, and the reason is printed on every run.
Reported when a list() row callback puts a field nothing can track into the markup, so a reused row keeps the old value.
A row is rebuilt when something it READ has moved, and the reads are recorded while the callback runs — every signal, at any depth, in any module. A plain field is not a signal, so there is nothing to record and nothing marks the row.
render() gets away with the same read because it runs whole and re-reads everything. A
reused row does not run at all, which is why one field can show two values in one component.
Two fixes, and the first is a word:
@state label = 'old'; // recorded, so the row wakes up
list(this.tasks, (t) => <li>{this.label}</li>) // inline: every row rebuilds anyway
An inline callback costs a callback call and a vnode per row per render, and — measured at 10 000 rows over five re-renders — no extra DOM work at all, so a short list pays nothing you can notice.
A plain field is not the problem, and this does not ask you to stop using one. It is the
only home for anything that cannot be JSON — a WebSocket, an AbortController, a Map of
nodes — because @state and @persist are serialised into the page. None of those is
rendered, and a read that never reaches the markup is never reported.
When this is handed to a function, the reads happen in that function and this cannot
answer for them. Pass the values rather than the component, which is better code anyway: a
function taking the whole component can read anything. If it needs several, group them in a
method or a @compute on the class — that stays inside what this can follow.
If a stale value is what you want, say so and it goes quiet:
// ramonda-check-ignore a hover count, stale until the row rebuilds is fine
Next
- All rules — the other checks this one runs beside.
- Checking your app — how to run it, and what it proves.