vue-simple-portal
vue-simple-portal copied to clipboard
Unable to access `$refs`
I've run into a similar issue to #14 but the fix in there hasn't worked for me. I've got a dropdown component which is using the library popper.js
to manage the position of the element, and I'm looking to get this bit to render in the portal. My template looks like this
div(@mouseenter="onMouseEnter", @mouseleave="onMouseLeave")
div(ref="trigger", @click="onClick")
slot(name="trigger" :open="open")
app-button(v-bind="binds")
| Actions
app-icon.ml-6(:icon="open ? 'caretUp' : 'caretDown'")
teleport(selector="#mp-portal")
.dropdown.bg-white.shadow-lg.border.border-grey-20.py-2.z-50(ref="dropdown", v-if="open", v-click-outside="close")
.arrow(data-popper-arrow, v-if="pointing")
slot(name="content")
The .dropdown
element has a ref that I need to be able to use in order to initialise the popper library. This is being handled when the user clicks on the trigger
createPopper() {
const dropdown = this.$refs["dropdown"];
console.log(dropdown);
this.$nextTick().then(() => {
console.log("1", this.$refs["trigger"], this.$refs["dropdown"]);
this.popper = createPopper(this.$refs["trigger"], dropdown, {
placement: "bottom-end",
modifiers: [
{
name: "offset",
options: {
offset: this.pointing ? [0, 14] : [0, 0],
},
},
],
});
});
},
I've kept the console logs to show where I'm having issues, but basically both before the $nextTick
call and after it, the dropdown ref is undefined.
In issue #14 the fix was to wait for the next tick but this isn't working here
Update
As a workaround, I've tried setting an id
on the dropdown div, and found some unusual behaviour. Unsure whether this is related to the way portal is implemented, or our own code, but I have to wait for at least 2 ticks for it to work, or alternatively use setTimeout(fn, 0);
async createPopper() {
await this.$nextTick();
console.log(document.getElementById(this.dropdownId));
}
This result in no element being output, but this works
async createPopper() {
await this.$nextTick();
await this.$nextTick();
console.log(document.getElementById(this.dropdownId));
}
Or this
async createPopper() {
setTimeout(() => {
console.log(document.getElementById(this.dropdownId));
}, 0);
}
Both kinda feel like nasty hacks but this is the only thing that seems to be working for me
I think we need to document this as a caveat. A similar one exists in portal-vue.
The basic reason for needing 2 ticks is that the portal content itself it mounted on the first tick after the parent of the portal has rendered - and mounting will introduce another tick until the ref is actually in the DOM.