Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: invalidate store when mutated inside each block #10785

Merged
merged 2 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/khaki-ligers-sing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

fix: invalidate store when mutated inside each block
Original file line number Diff line number Diff line change
Expand Up @@ -2344,8 +2344,19 @@ export const template_visitors = {
each_type |= EACH_IS_STRICT_EQUALS;
}

// Find the parent each blocks which contain the arrays to invalidate
// TODO decide how much of this we want to keep for runes mode. For now we're bailing out below
// If the array is a store expression, we need to invalidate it when the array is changed
dummdidumm marked this conversation as resolved.
Show resolved Hide resolved
let store_to_invalidate = '';
if (node.expression.type === 'Identifier' || node.expression.type === 'MemberExpression') {
const id = object(node.expression);
if (id) {
const binding = context.state.scope.get(id.name);
if (binding?.kind === 'store_sub') {
store_to_invalidate = id.name;
}
}
}

// Legacy mode: find the parent each blocks which contain the arrays to invalidate
const indirect_dependencies = collect_parent_each_blocks(context).flatMap((block) => {
const array = /** @type {import('estree').Expression} */ (context.visit(block.expression));
const transitive_dependencies = serialize_transitive_dependencies(
Expand Down Expand Up @@ -2382,15 +2393,24 @@ export const template_visitors = {
'$.invalidate_inner_signals',
b.thunk(b.sequence(indirect_dependencies))
);
const invalidate_store = store_to_invalidate
? b.call('$.invalidate_store', b.id('$$subscriptions'), b.literal(store_to_invalidate))
: undefined;

const sequence = [];
if (!context.state.analysis.runes) sequence.push(invalidate);
if (invalidate_store) sequence.push(invalidate_store);

if (left === assignment.left) {
const assign = b.assignment('=', expression_for_id, value);
return context.state.analysis.runes ? assign : b.sequence([assign, invalidate]);
sequence.unshift(assign);
return b.sequence(sequence);
} else {
const original_left = /** @type {import('estree').MemberExpression} */ (assignment.left);
const left = context.visit(original_left);
const assign = b.assignment(assignment.operator, left, value);
return context.state.analysis.runes ? assign : b.sequence([assign, invalidate]);
sequence.unshift(assign);
return b.sequence(sequence);
}
};
};
Expand Down
11 changes: 11 additions & 0 deletions packages/svelte/src/internal/client/reactivity/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,17 @@ export function store_set(store, value) {
return value;
}

/**
* @param {import('#client').StoreReferencesContainer} stores
* @param {string} store_name
*/
export function invalidate_store(stores, store_name) {
const store = stores[store_name];
if (store.store) {
store_set(store.store, store.value.v);
}
}

/**
* Unsubscribes from all auto-subscribed stores on destroy
* @param {import('#client').StoreReferencesContainer} stores
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ok, test } from '../../test';

export default test({
skip_if_ssr: 'permanent',
html: `
<input type="checkbox">
<input type="checkbox">
<input type="checkbox">
0
`,

async test({ assert, target, window }) {
const input = target.querySelector('input');
ok(input);

input.checked = true;
await input.dispatchEvent(new window.Event('change', { bubbles: true }));

assert.htmlEqual(
target.innerHTML,
`
<input type="checkbox">
<input type="checkbox">
<input type="checkbox">
1
`
);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { derived, writable } from "svelte/store";

const checks = writable([false, false, false])
const countChecked = derived(checks, ($checks) => $checks.filter(Boolean).length)
</script>

{#each $checks as checked}
<input type="checkbox" bind:checked />
{/each}

{$countChecked}
Loading