1

If you run this demo and click "modify in child", the text gets updated. But if you click "modify top level through slot", then it doesn't get updated, and after clicking it clicking the other button no longer works.

How can I update a top-level property of the slot? For example a boolean or string. Doing it directly in the child works, but I can't do it through the slot.

If the child data contains an object, I can modify a sub property of that data object through the slot (see the original version of this question before the edits for a demo of that), but I can't modify a top level property.

const Child = {
  template: `<div>
          {{ object }}
          <slot name="named" v-bind="object">
          </slot>
          <button @click="click">child</button>
        </div>`,
  data() {
    return {
      object: {
        string: "initial"
      }
    }
  },
  methods: {
    click() {
      this.object.string = "modify in child"
    }
  }
}
new Vue({
  components: {
    Child,
  },
  template: `
          <div class="page1">
            <Child>
              <template v-slot:named="slot">
                <button @click="click(slot)">modify top level through slot</button>
              </template>
            </Child>
          </div>`,
  methods: {
    click(slot) {
      slot.string = "updated top level through slot"
    }
  }
}).$mount('#app')
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
user779159
  • 9,034
  • 14
  • 59
  • 89

1 Answers1

0

As to why you are seeing this behavior where you can't modify the root object, but can modify properties underneath it, the exposed slot variable is a shallow clone, so the top level reference is not the same as the object reference in the child component. I've added a console.log and wrapped .string in an object to show it. Click child and then modify buttons.

So it looks like you are trying to expose the state of the child component to the parent component, so you can reach in from the parent and mutate the state of the child. This is generally not the way you are supposed to use Vue. The idea is that state should be moved higher up in the tree, and deterministic props are propogated down through your tree of components.

Directly referencing and mutating state on child components is an antipattern. This is to encourage designing components that have deterministic behavior and to maintain decoupling of the components (to keep them standalone and reusable). There's also some performance benefits.

This guy explains it well: https://stackoverflow.com/a/31756470/120242
Vue and React are based on similar concepts.

const Child = {
  template: `<div>
          {{ object }}
          <slot name="named" v-bind="object">
          </slot>
          <button @click="click">child</button>
        </div>`,
  data() {
    return {
      object: {
        string: {x: "initial"}
      }
    }
  },
  methods: {
    click() {
      window.ChildObjectReference = this.object;
      this.object.string.x = "modify in child"
    }
  }
}
new Vue({
  components: {
    Child,
  },
  template: `
          <div class="page1">
            <Child>
              <template v-slot:named="slot">
                <button @click="click(slot)">modify top level through slot</button>
              </template>
            </Child>
          </div>`,
  methods: {
    click(slot) {
      console.log( `slot: `,slot, `\nobject = `, window.ChildObjectReference,
         `\nslot !== ref as object: `, slot === window.ChildObjectReference,
         `\nslot.string === ref object.string: `, slot.string === window.ChildObjectReference.string)
    }
  }
}).$mount('#app')
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>

Displaying interaction between slot and parent component:

const Child = {
  template: `<div>
          {{ object }}
          <slot name="named" v-bind="object">
          </slot>
          <button @click="click">child</button>
        </div>`,
  data() {
    return {
      object: {
        string: "initial"
      }
    }
  },
  methods: {
    click() {
      this.object.string = "modify in child"
    }
  }
}
new Vue({
  data: {
    topLevelObject: { property: "top level initial" }
  },
  components: {
    Child,
  },
  template: `
          <div class="page1">
            <Child>
              <template v-slot:named="slot">
                <div>this is v-bind:'object' on slot 'named' put into variable slot: {{ slot }}</div>
                <button @click="click(slot)">modify top level through slot</button>
              </template>
            </Child>
            Top Level state: {{ topLevelObject }}
          </div>`,
  methods: {
    click(slot) {
      this.topLevelObject.property = "slot.string pushed to top level: " + slot.string
    }
  }
}).$mount('#app')
<div id="app"></div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js"></script>
user120242
  • 14,918
  • 3
  • 38
  • 52
  • That was deliberate. My question is asking how to modify a TOP LEVEL data of the slot object. – user779159 Apr 20 '20 at 11:21
  • I'm not sure what you mean by "top level data of the slot object". I may still be misunderstanding what you are trying to do. slot is essentially a binding to a prop in a child component, usually associated in such a way that allow bindings from the template to the slot in the child component. – user120242 Apr 20 '20 at 12:50
  • Top level means a property directly under the `slot` object. For example `slot.property` rather than `slot.sub.property`. The first doesn't work but the second does when it comes to updating. – user779159 Apr 20 '20 at 13:00
  • Okay so there's two issues here. One is that new property additions are not tracked by Vue's default observers. https://vuejs.org/v2/guide/reactivity.html#For-Objects You are referring to `.property` but it doesn't exist. It appears you are trying to mutate the slot object derived from state, hence trying to reach in and mutate the child's state directly from the parent. Is that intentional? – user120242 Apr 20 '20 at 13:05
  • For the first issue it doesn't work with `Vue.$set` either. For the second `.property` is just an example. I just removed the unnecessary `slot.property` from the example code. – user779159 Apr 20 '20 at 13:13