From 66b4d95ee3a0f26b547279019e1be09ed6e5d7b2 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 17 Jul 2024 19:11:27 +0100 Subject: [PATCH] chore: add warning when using non-reactive objects as bindable props --- .changeset/silent-eagles-roll.md | 5 +++ .../messages/client-warnings/warnings.md | 4 ++ .../svelte/src/internal/client/validate.js | 39 ++++++++++++++++++- .../svelte/src/internal/client/warnings.js | 13 +++++++ 4 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 .changeset/silent-eagles-roll.md diff --git a/.changeset/silent-eagles-roll.md b/.changeset/silent-eagles-roll.md new file mode 100644 index 0000000000..c629259ba8 --- /dev/null +++ b/.changeset/silent-eagles-roll.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: add warning when using non-reactive objects as bindable props diff --git a/packages/svelte/messages/client-warnings/warnings.md b/packages/svelte/messages/client-warnings/warnings.md index 2543a2065a..01952aae31 100644 --- a/packages/svelte/messages/client-warnings/warnings.md +++ b/packages/svelte/messages/client-warnings/warnings.md @@ -1,3 +1,7 @@ +## bindable_prop_not_reactive + +> The `%name%` prop was passed an object that isn't reactive yet was marked as bindable. The object should be either made reactive using `$state` or should contain properties that have a `set` accessor. + ## hydration_attribute_changed > The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value diff --git a/packages/svelte/src/internal/client/validate.js b/packages/svelte/src/internal/client/validate.js index 5c6f6a6553..ff4fd402e8 100644 --- a/packages/svelte/src/internal/client/validate.js +++ b/packages/svelte/src/internal/client/validate.js @@ -1,7 +1,16 @@ import { untrack } from './runtime.js'; -import { get_descriptor, is_array } from '../shared/utils.js'; +import { + array_prototype, + get_descriptor, + get_descriptors, + get_prototype_of, + is_array, + object_prototype +} from '../shared/utils.js'; import * as e from './errors.js'; +import * as w from './warnings.js'; import { FILENAME } from '../../constants.js'; +import { STATE_SYMBOL } from './constants.js'; /** regex of all html void element names */ const void_element_names = @@ -67,6 +76,30 @@ export function validate_each_keys(collection, key_fn) { } } +/** + * @param {any} value + */ +function is_possiblly_reactive_object(value) { + if (STATE_SYMBOL in value) { + return true; + } + const prototype = get_prototype_of(value); + if (prototype === array_prototype || prototype === object_prototype) { + const descriptors = get_descriptors(value); + for (let key in descriptors) { + var prop_value = value[key]; + if ( + descriptors[key].set || + (typeof prop_value === 'object' && + prop_value !== null && + is_possiblly_reactive_object(prop_value)) + ) + return true; + } + } + return false; +} + /** * @param {Record} $$props * @param {string[]} bindable @@ -87,5 +120,9 @@ export function validate_prop_bindings($$props, bindable, exports, component) { e.bind_not_bindable(key, component[FILENAME], name); } } + var value = $$props[key]; + if (typeof value === 'object' && value !== null && !is_possiblly_reactive_object(value)) { + w.bindable_prop_not_reactive(key); + } } } diff --git a/packages/svelte/src/internal/client/warnings.js b/packages/svelte/src/internal/client/warnings.js index e339cf1a8c..1e91bcf8d4 100644 --- a/packages/svelte/src/internal/client/warnings.js +++ b/packages/svelte/src/internal/client/warnings.js @@ -5,6 +5,19 @@ import { DEV } from 'esm-env'; var bold = 'font-weight: bold'; var normal = 'font-weight: normal'; +/** + * The `%name%` prop was passed an object that isn't reactive yet was marked as bindable. The object should be either made reactive using `$state` or should contain properties that have a `set` accessor. + * @param {string} name + */ +export function bindable_prop_not_reactive(name) { + if (DEV) { + console.warn(`%c[svelte] bindable_prop_not_reactive\n%cThe \`${name}\` prop was passed an object that isn't reactive yet was marked as bindable. The object should be either made reactive using \`$state\` or should contain properties that have a \`set\` accessor.`, bold, normal); + } else { + // TODO print a link to the documentation + console.warn("bindable_prop_not_reactive"); + } +} + /** * The `%attribute%` attribute on `%html%` changed its value between server and client renders. The client value, `%value%`, will be ignored in favour of the server value * @param {string} attribute