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: spread as a dependency for every prop #11290

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 43 additions & 11 deletions packages/svelte/src/internal/client/reactivity/props.js
Expand Up @@ -148,23 +148,47 @@ export function legacy_rest_props(props, exclude) {
* that looks like `() => { dynamic: props }, { static: prop }, ..` and wraps
* them so that the whole thing is passed to the component as the `$$props` argument.
* @template {Record<string | symbol, unknown>} T
* @type {ProxyHandler<{ props: Array<T | (() => T)> }>}}
* @type {ProxyHandler<{ props: Array<T | (() => T)>, keys: Array<import('#client').Value<string> | null> }>}}
*/
const spread_props_handler = {
get(target, key) {
let i = target.props.length;
var i = target.props.length;

while (i--) {
let p = target.props[i];
if (is_function(p)) p = p();
if (typeof p === 'object' && p !== null && key in p) return p[key];
var p = target.props[i];

if (is_function(p)) {
// untrack, so we don't update `key` unnecessarily
var value = untrack(p);

if (value && typeof value === 'object' && key in value) {
return p()[key]; // start tracking
} else {
get(/** @type {import('#client').Value} */ (target.keys[i]));
}
} else if (key in p) {
return p[key];
}
}
},
getOwnPropertyDescriptor(target, key) {
let i = target.props.length;
var i = target.props.length;

while (i--) {
let p = target.props[i];
if (is_function(p)) p = p();
if (typeof p === 'object' && p !== null && key in p) return get_descriptor(p, key);
var p = target.props[i];

if (is_function(p)) {
// untrack, so we don't update `key` unnecessarily
var value = untrack(p);

if (value && typeof value === 'object' && key in value) {
return get_descriptor(p(), key); // start tracking
} else {
get(/** @type {import('#client').Value} */ (target.keys[i]));
}
} else if (key in p) {
return get_descriptor(p, key);
}
}
},
has(target, key) {
Expand All @@ -191,11 +215,19 @@ const spread_props_handler = {
};

/**
* @param {Array<Record<string, unknown> | (() => Record<string, unknown>)>} props
* @param {any[]} props
* @returns {any}
*/
export function spread_props(...props) {
return new Proxy({ props }, spread_props_handler);
const keys = props.map((p) => {
if (is_function(p)) {
return derived(() => Object.keys(p() ?? {}).join(','));
}

return null;
});

return new Proxy({ props, keys }, spread_props_handler);
}

/**
Expand Down
@@ -0,0 +1,7 @@
<script>
let { x } = $props();

$effect(()=>{
console.log(x);
});
</script>
@@ -0,0 +1,37 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
html: `
<button>reset</button>
<button>add foo</button>
<button>update foo</button>
<button>update bar</button>
<button>update rest</button>
<button>update x</button>
`,
async test({ assert, target, logs }) {
const [reset, add_foo, update_foo, update_bar, update_rest, update_x] =
target.querySelectorAll('button');

assert.deepEqual(logs, ['something']);

flushSync(() => reset.click());
assert.deepEqual(logs, ['something']);

flushSync(() => add_foo.click());
assert.deepEqual(logs, ['something', 'something']);

flushSync(() => update_foo.click());
assert.deepEqual(logs, ['something', 'something']);

flushSync(() => update_bar.click());
assert.deepEqual(logs, ['something', 'something', 'something']);

flushSync(() => update_rest.click());
assert.deepEqual(logs, ['something', 'something', 'something', 'else']);

flushSync(() => update_x.click());
assert.deepEqual(logs, ['something', 'something', 'something', 'else', 'another']);
}
});
@@ -0,0 +1,13 @@
<script lang="ts">
import Test from "./Test.svelte";
let rest = $state({});
</script>

<button onclick={() => (rest = {})}>reset</button>
<button onclick={() => (rest = { foo: 1 })}>add foo</button>
<button onclick={() => (rest.foo = 1)}>update foo</button>
<button onclick={() => (rest.bar = 1)}>update bar</button>
<button onclick={() => (rest = { x: "else" })}>update rest</button>
<button onclick={() => (rest.x = "another")}>update x</button>

<Test x="something" {...rest}></Test>