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

Mounted slot vnode.el is null #815

Closed
jbruni opened this issue Mar 11, 2020 · 6 comments
Closed

Mounted slot vnode.el is null #815

jbruni opened this issue Mar 11, 2020 · 6 comments

Comments

@jbruni
Copy link

jbruni commented Mar 11, 2020

Version

3.0.0-alpha.8

Reproduction link

https://codepen.io/jbruni/pen/GRJQZpb

Steps to reproduce

  • There is a console.log at line 11 of the JavaScript code. It outputs the default slot's el.
  • If you open the Console (either codepen's built-in or browser's dev tool), you'll see the output is null.
  • It shouldn't be null. This is the bug happening. No more steps are required to reproduce.

What is expected?

The output should be the mounted slot's vnode el - corresponding to the <div>Hi, there</div> element.

What is actually happening?

The mounted slot's vnode el is null, instead of the DOM element.


The issue is related to the dynamic <component> used as the slot content.

If you go to line 17 of the Javascript code, and change #parent to #parent2, you will see the output in Console changes to <div>See you</div>. This is an example of the expected result. In this case, the working #parent2 template contains a simple div as the slot content, rather than a dynamic <component>.

NOTE: I am using vue-next version 3.0.0-alpha.8, but this version is not available for selection in the Version dropdown of new-issue.vuejs.org

@jbruni
Copy link
Author

jbruni commented Mar 11, 2020

It looks like this other issue (closed automatically) is related: #814

@KaelWD
Copy link
Contributor

KaelWD commented Mar 11, 2020

Duplicate of vuejs/vue#9580

You can do this but I'm not sure if there's a built-in way to then render the vnode in the template, you'd have to use a render function.

const slot = computed(() => slots.default())
onMounted(() => console.log(slot[0].el))
return { slot }

@yyx990803
Copy link
Member

This is expected, because calling slot functions technically always return fresh vnodes. The reason why #parent2 works is because the static slot content has been hoisted so it is returning the same (already mounted) vnode.

The idea here is that:

  1. You should not be relying on vnode.el in userland code. It's not documented public API and there is no guarantee about what the "expected behavior" is. The documented public API for accessing mounted DOM is via template refs.

  2. Maybe you shouldn't be messing with slot DOM nodes in the first place. Prefer transforming the slot vnodes at the render function level instead of attempting to mutate the end DOM imperatively.

  3. If you do have a use case that requires imperatively manipulating slot DOM nodes, what you can do is wrapping the slot with a div, and access that div (and its child nodes) via template ref. Still, I'd avoid this unless there is absolutely no better way around it.

@jbruni
Copy link
Author

jbruni commented Mar 11, 2020

Thanks, @yyx990803

What would be the recommended approach for adding a draggable behaviour which could be attached to any component?

For the consumer perspective:

  • a wrapper component, like <draggable><consumer-something /></draggable> ?
  • a custom directive, like <consumer-something v-draggable /> ?

My intention here, with the wrapper component approach, was to attach event listeners to the slot's mounted DOM. Draggable would be a "renderless", behaviour-only component.

Can I attach DOM event listeners by "transforming the slot vnodes at the render function level"?

If not, this means I must go by "wrapping the slot with a div"? Sounds like the solution for this "draggable" idea... 🤔 I don't see a better way around it.

Anyway, it will not be a "renderless" component anymore, because it will render this wrapping div.

I wonder if I really wanted a "renderless" solution... Something very trivial, like triggering an alert upon a mouseenter in the slot DOM element... Isn't this feasible?

(Well... "renderless" term may be misleading... because, after all, the component renders the slot within it...)

@KaelWD
Copy link
Contributor

KaelWD commented Mar 12, 2020

You can use mergeProps to add event listeners to a vnode.

@yyx990803
Copy link
Member

yyx990803 commented Mar 12, 2020

You can use the cloneVNode helper which accepts a second argument for extra props to be merged:

import { cloneVNode } from 'vue'

// in render fn
const slotContent = slots.default ? slots.default() : []
const transformedSlotContent = slotContent.map(vnode => cloneVNode(vnode, {
  onClick: () => { /* ... */ }
}))

However, I can imagine working on something like D&D can be a bit tough without free access to the DOM, so maybe wrapping slots with a wrapper element is still the easier way to do it.

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