Skip to content

Component two-way binding edge case #277

Closed
@Rich-Harris

Description

@Rich-Harris

This might be a wontfix (it's a niche problem, and it's very easily worked around) but I don't have time to fully investigate it right now, so just leaving myself some breadcrumbs:

<p>y: {{y}}</p>

{{#if x}}
  <Foo bind:y/>
{{else}}
  <Bar bind:y/>
{{/if}}

<Baz bind:x/>

<script>
  import Foo from './Foo.html';
  import Bar from './Bar.html';
  import Baz from './Baz.html';

  export default {
    components: {
      Foo,
      Bar,
      Baz
    }
  };
</script>

// Foo.html
<p>y: {{y}}</p>

<script>
  export default {
    data: () => ({
      y: 'foo'
    })
  };
</script>

// Bar.html
<p>y: {{y}}</p>

<script>
  export default {
    data: () => ({
      y: 'bar'
    })
  };
</script>

// Baz.html
<script>
  export default {
    data: () => ({
      x: true
    })
  };
</script>

That looks like it should result in this...

<p>y: foo</p>
<p>y: foo</p>

...but in fact it ends up like this:

<p>y: undefined</p>
<p>y: foo</p>

That's because on the initial pass, the {{else}} part is rendered, which initialises the binding between Main and Bar (because x doesn't yet have a value). Then, Svelte encounters the <Baz bind:x> component, and creates a new binding.

Bindings are flushed, which means that x has a value, which means that Bar is torn down (which has the effect of setting its _state to {}). Then the Bar binding is triggered, and y is set to undefined in Main.

As a starting point, no observer should be created when triggering a binding on a torn down component. (Perhaps observing a torndown component should also have no effect, or even throw — possibly a separate question.)

Changing the order of things in the template in Main.html yields different results:

<p>y: {{y}}</p>

<Baz bind:x/>

{{#if x}}
  <Foo bind:y/>
{{else}}
  <Bar bind:y/>
{{/if}}
<p>y: bar</p>
<p>y: bar</p>

(bar and not foo because by the time <Foo bind:y/> is created, y has a value in the parent component, and the child won't override the parent. Removing the phantom observer would presumably fix that as well.)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions