3.0.0-beta.4
https://codepen.io/lzhoucs/pen/rNOzpZa
The tab clicked should render its active prop as true
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
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:
mergeProps approach: https://codepen.io/lzhoucs/pen/MWaBMLzcloneVNode approach: https://codepen.io/lzhoucs/pen/OJywezpIn 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
Most helpful comment
First, as you have noticed, you don't really need the
_Tabswrapper - 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: