Reputation: 68720
CodeSandbox: https://codesandbox.io/s/61my3w7xrw?fontsize=14
I have this renderless component that uses a scoped slot:
name: "BlockElement",
props: {
element: {
type: Object,
required: true
}
},
data() {
return {
inputValue: this.element.value
};
},
render() {
return this.$scopedSlots.default({
inputName: this.inputName,
inputValue: this.inputValue
});
}
Using it like so:
<block-element :element="element" v-slot="{ inputName, inputValue }">
<div>
<input type="text" :name="inputName" v-model="inputValue">
<p>inputValue: {{ inputValue }}</p>
</div>
</block-element>
... so the value is not updated on change. What am I doing wrong?
Upvotes: 0
Views: 502
Reputation: 34306
In the following part of the template
<input type="text" :name="inputName" v-model="inputValue">
inputValue
is the variable obtained from the v-slot
and not the inputValue
computed property on the <block-element>
component; so if you assign to it (which is what v-model
does) it won't be calling the setter, it's just setting the value of a local variable in the template code.
You could "fix" it like this:
<block-element :element="element" v-slot="{ inputName }" ref="block">
<div>
<input type="text" :name="inputName" v-model="$refs.block.inputValue">
<p>inputValue: {{ $refs.block.inputValue }}</p>
</div>
</block-element>
but this is just messy and breaks the abstraction you tried to create.
Another way is to have a inputValue
setter property on the scope object that will correctly delegate the update to the component:
render() {
const self = this;
return this.$scopedSlots.default({
inputName: this.inputName,
get inputValue() { return self.inputValue },
set inputValue(value) { self.inputValue = value; },
});
}
<block-element :element="element" v-slot="scope">
<div>
<input type="text" :name="scope.inputName" v-model="scope.inputValue">
<p>inputValue: {{ scope.inputValue }}</p>
</div>
</block-element>
but this isn't ideal either because the scope object isn't typically writable, and this particular implementation detail would need to be documented.
In a situation like this where you want a scoped slot to pass data back to the parent component, you would implement this by passing a callback function to the slot. You can provide a function for setting inputValue
but then you can't use v-model
:
render() {
return this.$scopedSlots.default({
inputName: this.inputName,
inputValue: this.inputValue,
setInputValue: value => this.inputValue = value,
});
}
<block-element :element="element" v-slot="{ inputName, inputValue, setInputValue }">
<div>
<input type="text" :name="inputName" :value="inputValue" @input="setInputValue($event.target.value)">
<p>inputValue: {{ inputValue }}</p>
</div>
</block-element>
Now there's no confusion about what to do.
Upvotes: 1