Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vnode props can not seem to be updated from parent #1082

Closed
lzhoucs opened this issue Apr 29, 2020 · 8 comments
Closed

vnode props can not seem to be updated from parent #1082

lzhoucs opened this issue Apr 29, 2020 · 8 comments

Comments

@lzhoucs
Copy link

lzhoucs commented Apr 29, 2020

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 vuejs/vue#4766 (comment), 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

@underfin
Copy link
Member

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

Solution: You can should directly return children.

In vuejs/rfcs#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.

@lzhoucs
Copy link
Author

lzhoucs commented Apr 30, 2020

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

@underfin
Copy link
Member

sorry.I dont have another solution for this.

@lzhoucs
Copy link
Author

lzhoucs commented Apr 30, 2020

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.

@lzhoucs
Copy link
Author

lzhoucs commented May 1, 2020

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

@yyx990803
Copy link
Member

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 ac6a6f1 and 08bf7e3.

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
    })
  })
}

@lzhoucs
Copy link
Author

lzhoucs commented May 14, 2020

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

@lzhoucs
Copy link
Author

lzhoucs commented May 15, 2020

Not sure if you guys would still get notification after a ticket is closed. Just on the safe side, I opened a new ticket: #1186

@github-actions github-actions bot locked and limited conversation to collaborators Nov 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants