"Each block should have a 'key' attribute" warning in a paginated {#each} list
0 reputation · 05 Jun 2025, 06:08 UTC
0 reputation · 05 Jun 2025, 06:08 UTC
I'm building a paginated table in Svelte (Svelte 4, SvelteKit) where a load function reads ?page and limit search params, and the component slices a larger dataset with a reactive statement like $: displayed = allData.slice((page-1)*limit, page*limit). The slice renders through an {#each} block.
When I page through the results, the dev console shows the warning Each block should have a 'key' attribute for items that may change. I understand the warning relates to keyed updates, but my rows contain form inputs and a small child component with local state, and I'm unsure what counts as a stable key when the underlying records can be inserted or deleted between page changes.
My questions:
31775 reputation · 05 Jun 2025, 09:04 UTC
Use the record's unique ID as the key, not the array index. In your case the warning is not just cosmetic: because your rows contain form inputs and a stateful child component, an unkeyed (or index-keyed) {#each} can reuse the wrong component instance when the slice changes, leaving stale input values attached to the wrong record.
Use the record's stable unique identifier:
{#each displayed as row (row.id)}
<Row bind:value={row.note} />
{/each}The index is a property of the current slice, not of the record. Your slice is rebuilt on every page change, so index 0 on page 2 is a completely different record than index 0 on page 1. With index keys Svelte sees "same key, new data" and patches the existing DOM/component in place instead of tearing it down. That is exactly the scenario the warning exists for.
One nuance: if every page change truly destroys and recreates the whole list (e.g., the component remounts because the load function re-runs), index keys may appear to work. But that guarantee breaks the moment allData changes without a remount — an insert or delete between page changes, a reactive refresh, or overlapping records between adjacent pages. The ID key is correct under all of these; the index key is only correct under an assumption you don't control.
It's a correctness risk, not only performance. Svelte's unkeyed each-block updates by position: it mutates the existing row instances with new data rather than matching instances to records. Two concrete failure modes in your setup:
With (row.id) as the key, Svelte matches instances to records: a record that disappears has its row destroyed, and a new record gets a fresh instance. That is the behavior you want for stateful rows.
No. Keying is about record identity within the rendered list, not about how you fetched the list. Offset slicing and cursor-based pagination both produce "an array of records that may overlap with the previous render," so the record's unique ID remains the right key in both cases. Keyset pagination actually makes stable keys more valuable, since cursors are typically used with incremental/appended rendering where rows mount without a full rebuild — precisely where index keys fail.
(row.id) to the each block and confirm the dev-console warning disappears.allData between page changes.Assumption worth checking: this applies to Svelte 4 as you described. If you later migrate to Svelte 5, each blocks are keyed by default and the warning disappears, but providing an explicit key for stateful rows is still good practice.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.