Vue-next: vnode props can not seem to be updated from parent

Created on 30 Apr 2020  路  8Comments  路  Source: vuejs/vue-next

Version

3.0.0-beta.4

Reproduction link

https://codepen.io/lzhoucs/pen/rNOzpZa

Steps to reproduce

What is expected?

The tab clicked should render its active prop as true

What is actually happening?

The tab renders its initial active prop value false and never re-renders after being clicked


I am using a functional component Tabs to update its children Tab before handing it over to _Tabs because I don't know how to do the same directly in _Tabs:

const _Tabs = {
  template: '<div><slot></slot></div>'
}

const Tabs = (props, {emit, slots}) => {
  const children = slots.default && slots.default()

  children.forEach(vnode => {
    vnode.props = Vue.mergeProps(vnode.props, {
      onActivate(newHref) {
        emit('update:modelValue', newHref)
      },
      active: vnode.props.href === props.modelValue
    })
  })
  return Vue.h(_Tabs, () => children)
}

Using other approaches such as scoped slots might work however that's less ideal because the user who uses this component has to do extra work besides below:

  <tabs v-model="selectedTab">
    <tab href="#tab1">Tab 1</tab>
    <tab href="#tab2">Tab 2</tab>
  </tabs>

In my opinion, handling tab click and activate/deactivate tab should be the tab(s) component's job as a library, and the user only provides v-model for initial state and gets notified when selected is updated.

According to https://github.com/vuejs/vue/issues/4766#issuecomment-274347398, this should work in vue 2 but I didn't try.

I also tried the cloneVNode approach as oppose to mutate the vnode directly, but it didn't work for me

Most helpful comment

First, as you have noticed, you don't really need the _Tabs wrapper - which already makes it work. But your original case still helped me identify 2 bugs which are fixed in ac6a6f11 and 08bf7e36.

Note that even with the fixes, you should never directly mutate a vnode in Vue 3: vnodes should be considered immutable (as they may be a compiler-hoisted vnode). Instead, use cloneVNode:

const Tabs = (props, {emit, slots}) => {
  const children = slots.default ? slots.default() : []
  returnh('div', children.map(vnode => {
    return cloneVNode(vnode, {
       // ...props
    })
  })
}

All 8 comments

const Tabs = (props, {emit, slots}) => {
// ...
  return children
}

Solution: You can should directly return children.

In https://github.com/vuejs/rfcs/pull/154.

Functional components without props declaration will have everything passed in as props. It will only have implicit fallthrough for class, style and v-on listeners.

The props with modelValue will omit with use _Tabs and this caused _Tabs not trriger re-render.

Sorry, I don't quite get it. I don't expect/rely on anything being fallthrough though. All I know isprops.modelValue is available inside the render function(Tabs), and the render function runs everytime the tab is clicked (because of updated props.modelValue I guess). So maybe there's something I missed that I still don't get.

Is there a solution without returning children directly because I can't skip _Tabs which contains logic and styling etc. I can probably convert _Tabs into render function inside Tabs: https://codepen.io/lzhoucs/pen/rNOzrzK, however I do prefer SFC if possible. And I still don't understand why I have to do that for the re-render to work

sorry.I dont have another solution for this.

Thanks for the help so far. I feel like somehow the modified vnodes(Tab) are not getting picked up or dropped when they are not the direct children of Tabs, in my case _Tabs being the layer in between. Modifying them as direct children works which is what I did in my 2nd example above: https://codepen.io/lzhoucs/pen/rNOzrzK

In other words, there seems no way to modify indirect child nodes in vue, which kind of makes sense. I didn't intend to use the extra component either, I was trying to do everything in SFC _Tabs but I didn't know how to do it, more specifically, how to let template '<div><slot></slot></div> render modified default slot instead of the original one. I attempted to do the following in _Tabs but it didn't work:

const Tabs = {
  template: '<div><slot></slot></div>',
  setup(props, context) {
     const children = context.slots.default && context.slots.default()

    children.forEach(vnode => {
    // modify vnode
    })
    context.slots.default = () => children
  }
}

I got a warning in the console:

Set operation on key "default" failed: target is readonly.

I guess I'll stick to the render function Tabs for now.

Update, I had some luck with SFC finally. It turns out I can totally get rid of <slot></slot> in my template. Check it out: https://codepen.io/lzhoucs/pen/RwWZYGp

First, as you have noticed, you don't really need the _Tabs wrapper - which already makes it work. But your original case still helped me identify 2 bugs which are fixed in ac6a6f11 and 08bf7e36.

Note that even with the fixes, you should never directly mutate a vnode in Vue 3: vnodes should be considered immutable (as they may be a compiler-hoisted vnode). Instead, use cloneVNode:

const Tabs = (props, {emit, slots}) => {
  const children = slots.default ? slots.default() : []
  returnh('div', children.map(vnode => {
    return cloneVNode(vnode, {
       // ...props
    })
  })
}

@yyx990803 thanks for the reply. I know this ticket is closed but I do have a follow up question please. Suppose now I need to access Tab's active prop inside Tabs, I found no way to do it. I've tried both:

In both cases, the console.log(vnode.props.active) shows undefined when either tab is clicked, even though vnode.props.active can be set properly from parent which is this ticket's original expected behavior.

I've updated to the latest version v3.0.0-beta.12 BTW.

Not sure if you guys would still get notification after a ticket is closed. Just on the safe side, I opened a new ticket: https://github.com/vuejs/vue-next/issues/1186

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Jexordexan picture Jexordexan  路  4Comments

HakamFostok picture HakamFostok  路  3Comments

crutchcorn picture crutchcorn  路  3Comments

chrisvfritz picture chrisvfritz  路  4Comments

adamberecz picture adamberecz  路  3Comments