svelte
svelte copied to clipboard
<Todo bind:this={todo.ref} ... /> leads to undefined when todos array is mutated
Describe the bug
While initially all todo.ref class fields are set properly, as soon as I mutate the todos array (e.g. when doing todos.splice(1,0, new Todo('Clean toilet')) the todo.ref is undefined. I had a really hard time reproducing this, as for other indexes it works (like when I do todos.push(new Todo('Clean toilet')).
Reproduction
https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAE51UwY7bOAz9FcJbwA4Q2N1rmgx221MPu5ddoIfJoNVYdKyOQhkSPdnC8L8vKNlOMkinRQ9JFOqRIh8fOWSNsRiyzf2QkTpitsn-7LpsnfG3Tv6EZ7SM2ToLrve1WLah9qbjuz3t2Rw75xn-ddr95TRaaLw7Ql5Wi6VMAcqvIX_3wuMKPOGuQAOwqZ9gTMD9lMs-ixj5WGRgp12AHbwJrBiL-3jHhKdzVkX-SYUWtAkthny1vg0xHUJjnfPfQ7z3hg7gegb2KrT5SlAPK8mG9qzCN6qh6alm4wiU1p8JT58lvWIFQ4pYVWAooI-QzgUjh3RVOwosNtjB7--gqqA4GW7Fsos1lhbpwC0YhpPzT0EeBoBEQBk6a2oskgnEaz2f3y6nFwV9sKgI2BmLHIsWzCqxy-lbnZTh2IZiFZOK_3tiY4FbhLpVdEBoVYDOu04dFKMGRRpqd-wcIXGAVj0jPCISeCSNHvXCxj-I8GhIb7g1YTdILaXHZgRDcCGLM0XOYmndociX-OCxAddAY3xIctjk68TK_dsHiVa2aK0rVnNpVQXyHKD3zgd4xFr1ASeXzoXoBCZATxobQ3O-r78v5EoIMOGcwBztOoVxT9vqPEa0feyZHYGj2pr6aTdcqme8-yDGbZVAd3safkNVt5PyVYiHNZhRQm_jZEUiR6gEXAl43FO2zo5Om8agzjbsexzXy8iL0y_M_Cevug791SBPtlvz_JEI_dXkL5YreBqFIbE5ymyLtkIh5F3zdvFgzG2bwmzOzUm6Wt4ZZ2puiq66E91vq-uwPyLucs-dSfwaLgnE_yIDGhvVW4baqhDSEoybQaKdd1ieJ5mIsF4aZ3p8X7PzhTgu20XKKadQ8jNrbXytgoWaX-j_9zf4ay2U-4mPZV1O4zFX4pF7T_AlmtMrb1KfJOvxy-0p0uY5Zvi3OuIGLvAClMsfdXLq-c8yMRdZt8Zqj_S6VidposXjWZi5Ns_5GKMNf6TtuIQrVqnCK78bNTyM_wMV3w_ixQcAAA==
Logs
running Svelte compiler version 5.0.0-next.110
103Fetch finished loading: GET "<URL>".
VM351 about:srcdoc:96 component ref of first todo: hello from Wash dishes
playground:output:3492 Uncaught (in promise) TypeError: todos[pos].ref.hello is not a function
at HTMLButtonElement.add_new_todo (playground:output:3492:63)
System Info
System:
OS: macOS 14.2.1
CPU: (8) arm64 Apple M2
Memory: 107.00 MB / 8.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 20.10.0 - /usr/local/bin/node
npm: 10.2.3 - /usr/local/bin/npm
Browsers:
Chrome: 124.0.6367.62
Safari: 17.2.1
npmPackages:
svelte: 5.0.0-next.110 => 5.0.0-next.110
Severity
blocking all usage of svelte
With a keyed each block it works: https://svelte-5-preview.vercel.app/#H4sIAAAAAAAAA51UTY_TMBD9K6OAlESqEriWdsXXhQNcAHHYrsAbTxqz7jiyJ1tWUf47sp2kDZQFcWjrjt98Pb-ZPqmVRpesr_uExAGTdfKqbZNVwg-t_-PuUTMmq8SZzlbesnGVVS1f7WjH6tAay_DJSPPeSNRQW3OAtChnSxEDFN9d-uIXjwV4xC1APbCq7mCIwN1Yyy4JGP_RyMBGGgdbeOpYMGbX4Y4Jj6eqsvSLcA1I5Rp0ab66DFEtQq2NsX9CvLaK9mA6BrbCNWnuUTe5r4Z2LNwDVVB3VLEyBELKr4THr768LIc-RixLUOTQBkhrnPKHeFUZcuxtsIXnL6AsITsqbrxlG3osNNKeG1AMR2PvnE8MAJGAwrVaVZhFE3iv1XR-Np9-aeiNRkHARmnk0LTH5JFdjt_iKBSHZ8jyUFT43xErDdwgVI2gPUIjHLTWtGIvGCUIklCZQ2sIiR004h7hFpHAIkm0KGc2PiLCrSK55ka5be97KSzWAyiCM1mcKDIaC232WTrHB4s1mBpqZV2UwzpdRVaun934aEWDWpssn1orS_DpAK011sEtVqJzOLq0xgUnUA46klgrmup9PL8n14cA5U4FTNGWJQw72pSnMaLNbcdsCAxVWlV32_5cPcPVG2_clBF0taP-CYqqGZUvXDisQEEW6FMyH3ySTZixQOkApXcrvduwo2SVHIxUtUKZrNl2OKzm4fdO_zH9X6xoW7SLkR5tlyb7HRHaxQ6YLQt4HIo-8jr4Kfcqc5mnccngWcJQ2yaGWZ-eKSpszjNM1FyUX3nlJ2BTLsP-jbjzjXci8bs7JxB_BAYk1qLTDJUWzsV1GHaEj3baZmkaBeMl9ptRSdhCZR9aNoUVJM3h8-d3b7NpIQXubFexsZmPOi8h32sx5vE_kySHx9qbefsPcfx50T_2vv5-JGvequMUTZ1Y5M4SfAvmmOVpfERf9fDt8rBJdR8q_CAOuIYzvAf6y7898yiIf2ViarJqlJYW6XEhj7pFjYeTalOp7tMhROtfxiU6h8vy2OHC70IPN8NP41USPewHAAA=
This also kinda explain why position matters. At .length you are pushing in a completely new position. With splice you are "replacing an old one" so the component at position 2 is not unmounted but the prop is replaced and the same goes with the component inside. Is the same component so bind:this is probably not triggered.
Not sure if this is still a bug tho.
Thanks a lot for spotting this! We can of course provide a unique key when that solves the issue. Still, I guess I'd expect this to work without providing a key too (with less efficient reconciliation).
So leaving this open for the Svelte team to decide if there's action needed (could be a fix, or a warning?).
Attaching our Twitter discussion for context:
https://twitter.com/PaoloRicciuti/status/1782036498780414003
The behavior is the same in Svelte 4, it doesn't work there, too.
I'm inclined to mark this as "works as designed". The reason is that the each array by default is checking the array index for whether or not the whole thing needs to be recreated. Since the indexes only increase, the existing ones never change and so bind:this has no indication to update.
Yeah, this is working as intended. Using keyed each blocks is always the preferred way to handle stateful content FWIW.