Setup() can not binding v-model correctly with ref variable in h()
Version
3.2.33
Reproduction link
Steps to reproduce
This is my custom component in components/HelloWorld.vue
<template>
<slot></slot>
<slot name="foo"></slot>
<slot name="bar"></slot>
<div class="hello">
<h1>{{ msg.value }}</h1>
</div>
</template>
<script>
import { ref, toRefs } from 'vue';
export default {
name: 'HelloWorld',
props: {
'onUpdate:msg': Function,
msg: String,
},
setup(props, { emit }) {
emit('update:msg', 'MyEvent123456');
},
};
</script>
If I put the HelloWorld in template of App.vue, it works normally:
https://stackblitz.com/edit/vue-dfq6hg?file=src%2FApp.vue
<template>
<HelloWorld v-model:msg="msg">
default slot
<template v-slot:foo>
<div @click="callAlert">foo</div>
</template>
<template v-slot:bar> <span>one</span><span>two</span> </template>
</HelloWorld>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
import { ref } from 'vue';
export default {
name: 'App',
components: {
HelloWorld,
},
setup() {
const msg = ref('Hello world');
function callAlert() {
alert('test');
}
return {
msg,
callAlert,
};
},
};
</script>
But, If I render HelloWorld.vue with h() in setup(), it show warning, And the page shows "MyEvent123456"
not MyEvent123456
:
[Vue warn]: Invalid prop: type check failed for prop "msg". Expected String with value "[object Object]", got Object at <HelloWorld ref="test" msg=Ref< "Hello world" > onUpdate:msg=fnonUpdate:msg >
https://stackblitz.com/edit/vue-zvcbr6?file=src%2FApp.vue
<template></template>
<script>
import HelloWorld from './components/HelloWorld.vue';
import { h, createApp, ref, getCurrentInstance } from 'vue';
function renderDynamicComponet(componentType, props = {}, slots = {}) {
const { appContext } = getCurrentInstance();
const component = h(componentType, props, slots);
const div = document.createElement('div');
document.querySelector('#app').appendChild(div);
const app = createApp(component, props);
Object.assign(app._context, appContext);
const instance = app.mount(div);
return {
app,
instance,
};
}
export default {
name: 'App',
setup() {
const msg = ref('Hello world');
function callAlert() {
alert('test');
}
const component = renderDynamicComponet(
HelloWorld,
{
ref: 'test',
msg,
'onUpdate:msg': (e) => {
msg.value = e;
},
},
{
default: () => 'default slot',
foo: () =>
h(
'div',
{
onClick: callAlert,
},
'foo'
),
bar: () => [h('span', 'one'), h('span', 'two')],
}
);
console.log(component.instance);
return {
msg,
};
},
};
</script>
If I try to pass normal string to msg, the msg is not reactive:
https://stackblitz.com/edit/vue-werktf?file=src%2FApp.vue
<template></template>
<script>
import HelloWorld from './components/HelloWorld.vue';
import { h, createApp, ref, getCurrentInstance } from 'vue';
function renderDynamicComponet(componentType, props = {}, slots = {}) {
const { appContext } = getCurrentInstance();
const component = h(componentType, props, slots);
const div = document.createElement('div');
document.querySelector('#app').appendChild(div);
const app = createApp(component, props);
Object.assign(app._context, appContext);
const instance = app.mount(div);
return {
app,
instance,
};
}
export default {
name: 'App',
setup() {
let msg = 'Hello world';
function callAlert() {
alert('test');
}
const component = renderDynamicComponet(
HelloWorld,
{
ref: 'test',
msg,
'onUpdate:msg': (e) => {
msg = e;
},
},
{
default: () => 'default slot',
foo: () =>
h(
'div',
{
onClick: callAlert,
},
'foo'
),
bar: () => [h('span', 'one'), h('span', 'two')],
}
);
console.log(component.instance);
return {
msg,
};
},
};
</script>
What is expected?
The msg result is MyEvent123456 not "MyEvent123456"
https://stackblitz.com/edit/vue-dfq6hg?file=src%2FApp.vue
What is actually happening?
It shows "MyEvent123456" and appears warning:
https://stackblitz.com/edit/vue-zvcbr6?file=src%2FApp.vue
Invalid prop: type check failed for prop "msg". Expected String with value "[object Object]", got Object at <HelloWorld ref="test" msg=Ref< "Hello world" > onUpdate:msg=fnonUpdate:msg >
You are passing the msg ref to the component, while you really want to pass msg.value
@LinusBorg However, if I pass ref to msg propety, there are some issue:
The msg result is "MyEvent123456" not MyEvent123456
There is an error said:
Invalid prop: type check failed for prop "msg". Expected String with value "[object Object]", got Object
at <HelloWorld ref="test" msg=Ref< "Hello world" > onUpdate:msg=fnonUpdate:msg >
How should I define this type of msg property? Object is not expected.
msg can't be auto unref in template. I need to use
msg.value
in template of HelloWorld.vue.If I pass msg.value to render function, it still not reactive. https://stackblitz.com/edit/vue-werktf?file=src%2FApp.vue
<script>
export default {
name: 'App',
setup() {
const msg = ref('Hello world');
function callAlert() {
alert('test');
}
const component = renderDynamicComponet(
HelloWorld,
{
msg: msg.value,
'onUpdate:msg': (e) => {
msg.value = e;
},
},
{
default: () => 'default slot',
foo: () =>
h(
'div',
{
onClick: callAlert,
},
'foo'
),
bar: () => [h('span', 'one'), h('span', 'two')],
}
);
console.log(component.instance);
return {
msg,
};
},
};
</script>
Are these normal features or just some bugs? How should I do if I want to bind reactive v-model correctly in the render function of setup() ?
- Because this is what a stringified ref looks like.
- The error happens because you pass a ref.
- If you want to pass a ref, use
type: Object
for the props definition. - If you don't want to pass a ref, then don't pass a ref.
- Yes, props are not unreffed, because they should not be refs, usually.
- Currently, props passed to
app.mount()
are non-reactive. The common workaround is to use a second wrapper component that passes the props to the actual component reactively during render. (this reminds me I still have a branch that should be a PR about this).
Please use chat.vuejs.org for further questions.
Currently, props passed to app.mount() are non-reactive.
You got the point.
Currently, we can bind the ref to v-model of custom component in <template>
:
<template>
<HelloWorld v-model:msg="msg">
</HelloWorld>
</template>
<script>
import HelloWorld from './components/HelloWorld.vue';
import { ref } from 'vue';
export default {
name: 'App',
components: {
HelloWorld,
},
setup() {
const msg = ref('Hello world');
return {
msg
};
},
};
</script>
But we can't do the same thing with render function before I mount it programmatically.
Recently, I need to make a component which can be put in<template></template>
, and also can be mounted programmatically with render function, while I need to bind a reactive v-model property.
That's why I posted this issue yesterday.