Subscribe on changes!

Setup() can not binding v-model correctly with ref variable in h()

avatar
Apr 19th 2022

Version

3.2.33

Reproduction link

stackblitz.com

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 >

avatar
Apr 19th 2022

You are passing the msg ref to the component, while you really want to pass msg.value

avatar
Apr 20th 2022

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:

  1. The msg result is "MyEvent123456" not MyEvent123456 image

  2. 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.

image

  1. msg can't be auto unref in template. I need to use msg.value in template of HelloWorld.vue.

  2. 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() ?

avatar
Apr 20th 2022
  1. Because this is what a stringified ref looks like.
  2. 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.
  1. Yes, props are not unreffed, because they should not be refs, usually.
  2. 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.

avatar
Apr 20th 2022

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.

avatar
Apr 20th 2022

Then this can stay closed as a duplicate of #4874