You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The documentation regarding two-way bindings for component properties is very vague. It says you can bind to component props using the same syntax as for elements, but doesn't mention how these bindings interact with the parent component and other reactive variables.
I created some test apps and stepped through the debugger until I had a decent idea of what was happening. Here's my understanding of what goes on in this REPL:
For readability, component.$$.dirty is not an array of bitfields but a Set of strings and component.$$.bound uses strings (prop names) as keys.
Button clicked in Inner
$$invalidate for local reactive variable "localTrig"
dirty_components.push(Inner)
schedule_update()
Inner.$$.dirty.add("localTrig")
Microtask to update dirty components is called (flush)
dirty_components, flushidx: [->Inner<-]
.$$.dirty.has("localTrig")
$$invalidate for exported variable "outValue"
Two-way binding detected, call $$.bound["outValue"](outValue)
$$invalidate for App's reactive variable "value"
dirty_components.push(App)
app.$$.dirty.add("value")
.$$.dirty.add("outValue")
.$$.dirty.has("outValue")
console.log(...)
dirty_components, flushidx: [Inner, ->App<-]
.$$.dirty.has("value")
console.log(...)
app.$$.fragment.p
inner_changes = {}
!updating_outValue && dirty.has("value")
updating_outValue = true
inner_changes["outValue"] = value
add_flush_callback(() => updating_outValue = false)
inner.$set(inner_changes)
Check that inner_changes is not empty
inner.$$.skip_bound = true
inner.$$set(inner_changes)
$$invalidate for Inner's exported variable "outValue"
Two-way binding detected but skipped due to being called by SvelteComponent.$set
dirty_components.push(Inner)
inner.$$.dirty.add("outValue")
inner.$$.skip_bound = false
dirty_components, flushidx: [Inner, App, ->Inner<-]
.$$.dirty.has("outValue")
console.log(...)
Run flush callbacks:
updating_outValue = false
I'd like to know how much of this is:
Wrong
Internal behavior that shouldn't be relied upon
Guaranteed to keep working after an update
And if #5689 and #8184 ever get fixed, will it be considered a breaking change or a "you brought this on yourself by relying on unspecified behavior" change?
The second REPL tests how many updates can be sent between the parent and child components, and the answer seems to be two updates from the child and one update from the parent.
Not limiting the number of updates would allow something that looks a bit like a useEffect cascade if you squint, so this might be intentional. The presence of updating_outValue in App's $$.fragment.p also suggests it's intentional, but it might serve another purpose. Since this isn't documented, I'd still like a clarification.
The third REPL tests how the order of assignments in code outside the child component's $$.update function (for example, in an event handler) affects the order of reactive statements in the parent and the child.
Since bound variables are updated synchronously at the time of the assignment, the order of reactive variable assignments affects the order of component updates. Assigning a bound variable first will cause the parent component to be updated first, and assigning some other reactive variable will cause the child to be updated first.
The fourth REPL demonstrates how assignments to bound variables can be detected before the parent's $$.update function runs by binding the child's prop to a store with a custom set function.
Describe the proposed solution
Documentation addressing the following points (i.e. a guarantee the documented behavior won't silently break when the internals change):
What happens when a component with a bind: prop is mounted by a parent component? Is it guaranteed to cause a reactive update for the parent, even if the child doesn't change the value?
What happens when a child component updates a bound variable?
If updating outside a reactive statement, do the child's reactive statements run first or do the parent's?
Can the parent observe intermediate (i.e. not the final update to this particular variable in $$.update) changes to bound reactive variables?
If not, what happens when the parent component receives an event? Does it see the old values, or the new ones? Currently, this doesn't matter since the parent can observe changes, but if the behavior of two-way bindings is changed, this is something that should be addressed.
The order of on: and bind: props on "native" elements affects which version of the values are available to the event handlers. Currently, this does not apply to Svelte components.
Can the parent and child use two-way binding to update each other's reactive variables in a potentially infinite loop? If not, what's the limit?
Alternatives considered
N/A - this feature request is for documentation regarding two-way binding, and whether or not the current observable behavior is likely to change after an update.
Importance
nice to have
The text was updated successfully, but these errors were encountered:
to be honest most of this is somewhat unspecified behavior, some things are considered bugs. With Svelte 5 we'll make things more consistent and predictable - so I'm somewhat hesitant to write out all the nuances in behavior since some of them will change.
As promised, Svelte 5 will make this far easier to reason about. You can read more about the new behavior in the preview docs. TLDR: variables that Svelte directly connects to a $state rune are updated consistently, for everything else use fine-grained reactivity.
Describe the problem
The documentation regarding two-way bindings for component properties is very vague. It says you can bind to component props using the same syntax as for elements, but doesn't mention how these bindings interact with the parent component and other reactive variables.
I created some test apps and stepped through the debugger until I had a decent idea of what was happening. Here's my understanding of what goes on in this REPL:
App.svelte
Inner.svelte
For readability, component.$$.dirty is not an array of bitfields but a Set of strings and component.$$.bound uses strings (prop names) as keys.
I'd like to know how much of this is:
And if #5689 and #8184 ever get fixed, will it be considered a breaking change or a "you brought this on yourself by relying on unspecified behavior" change?
The second REPL tests how many updates can be sent between the parent and child components, and the answer seems to be two updates from the child and one update from the parent.
Not limiting the number of updates would allow something that looks a bit like a
useEffect
cascade if you squint, so this might be intentional. The presence ofupdating_outValue
in App's$$.fragment.p
also suggests it's intentional, but it might serve another purpose. Since this isn't documented, I'd still like a clarification.The third REPL tests how the order of assignments in code outside the child component's
$$.update
function (for example, in an event handler) affects the order of reactive statements in the parent and the child.Since bound variables are updated synchronously at the time of the assignment, the order of reactive variable assignments affects the order of component updates. Assigning a bound variable first will cause the parent component to be updated first, and assigning some other reactive variable will cause the child to be updated first.
The fourth REPL demonstrates how assignments to bound variables can be detected before the parent's
$$.update
function runs by binding the child's prop to a store with a customset
function.Describe the proposed solution
Documentation addressing the following points (i.e. a guarantee the documented behavior won't silently break when the internals change):
bind:
prop is mounted by a parent component? Is it guaranteed to cause a reactive update for the parent, even if the child doesn't change the value?$$.update
) changes to bound reactive variables?on:
andbind:
props on "native" elements affects which version of the values are available to the event handlers. Currently, this does not apply to Svelte components.Alternatives considered
N/A - this feature request is for documentation regarding two-way binding, and whether or not the current observable behavior is likely to change after an update.
Importance
nice to have
The text was updated successfully, but these errors were encountered: