LinusBorg / vue-simple-portal

A simpler Portal implementation focussed on moving slot content to the end of the body element
Other
223 stars 28 forks source link

Unable to access `$refs` #45

Open pareeohnos opened 4 years ago

pareeohnos commented 4 years ago

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

LinusBorg commented 3 years ago

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.