#each key with array blows up
#17721
Development PRs
Fixes #17721
Keyed #each blocks accept any object, but array-literal keys (e.g. ([thing.group, thing.id])) could crash on updates because get_key was called multiple times per update, producing new array instances and causing internal Map lookups to miss. This change computes keys once per update, stores them in key_list, and reuses them throughout reconcile() (including the animation prepass), preventing the runtime error even though array-literal keys remain inherently unstable for identity across updates.
Before submitting the PR, please make sure you do the following
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- Prefix your PR title with
feat:,fix:,chore:, ordocs:. - This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
- If this PR changes code within
packages/svelte/src, add a changeset (npx changeset).
Tests and linting
- Run the tests with
pnpm testand lint the project withpnpm lint
Summary
Fixes #17721
In dev mode, detect when a keyed each block has a key function that returns different values when called multiple times for the same item (non-idempotent). This catches the common mistake of using array literals like [thing.group, thing.id] as keys, which creates a new array object each time and will never match by reference.
- Adds new
each_key_volatileerror with helpful message explaining the issue - Checks key idempotency in the each block loop during dev mode
- Provides a clear error instead of the cryptic "Cannot read properties of undefined" that occurred previously
Issue
Describe the bug
The key can be any object
So I tried using array as a key for #each block:
{#each things as thing ([thing.group, thing.id])}
Works fine when page loads, but when data changes, it throws error.
Had to convert to string in order to get it working:
{#each things as thing ([thing.group, thing.id].join('-')}
So either docs need updating, or #each needs fixing to support array keys
Reproduction
https://svelte.dev/playground/1db5362a3e974b209b11ae389758c93f?version=5.51.2
Click button, check browser console.
Logs
playground:output:5619 Uncaught TypeError: Cannot read properties of undefined (reading 'e')
at reconcile (playground:output:5619:54)
at commit (playground:output:5450:4)
at Batch.process (playground:output:1109:46)
at flush_effects (playground:output:1484:11)
at Batch.flush (playground:output:1244:5)
at Array.eval (playground:output:1417:13)
at run_all (playground:output:126:10)
at run_micro_tasks (playground:output:739:3)
at eval (playground:output:757:32)
System Info
System:
OS: macOS 26.3
CPU: (14) arm64 Apple M4 Pro
Memory: 439.02 MB / 24.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.10.0 - /Users/kieran/.local/share/mise/installs/node/24.10.0/bin/node
npm: 11.6.1 - /Users/kieran/.local/share/mise/installs/node/24.10.0/bin/npm
pnpm: 10.29.3 - /Users/kieran/.local/share/mise/installs/node/24.10.0/bin/pnpm
npmPackages:
svelte: 5.51.0 => 5.51.0
Severity
annoyance
Info
No doubt the error needs to be fixed. However, even without the error you shouldn't do that. You are creating a new array literal each time. Which completely diminishes the idea of the key, which is to identify items to re-use the DOM nodes. With the array literal Svelte will compare referential equality of two different arrays, which is always false.
([] === []) === false
This should probably trigger a warning that your key expression is dynamic or something.
Pro tip: You can prefix GitHub URLs of issues, PRs or discussions with svcl.dev/ to view them on this page! Also try it on a GitHub release ;)