vue-instantsearch icon indicating copy to clipboard operation
vue-instantsearch copied to clipboard

Portal Vue + Vue InstantSearch Widgets

Open MrRossBennett opened this issue 6 years ago • 5 comments
trafficstars

Hello all

We're trying to move a bunch of our vue-instantsearch widgets( e.g <ais-clear-refinements />) to a separate sidebar component. The sidebar component is wrapped by the <ais-instant-search> widget. The sidebar component contains a slot which is wrapped in a PortalVue tag:

<Portal to="flyout">
    <slot />
</Portal>

So the vue-instantsearch widgets are passed into that slot. This results in the following error:

It looks like you forgot to wrap your Algolia search component "<ais-clear-refinements>" inside of an "<ais-instant-search>" component.

We've tried passing the Algolia searchClient into the sidebar component, but the same error occurs. When we remove the <Portal /> tag, it works fine. Any ideas?

Thanks!

MrRossBennett avatar May 15 '19 10:05 MrRossBennett

Can you create what you have so far in a sandbox environment? We have a template available for that purpose here. Thanks!

Haroenv avatar May 15 '19 10:05 Haroenv

Thanks, we'll do that.

Essentially though, if we wrap any vue-instantsearch widgets in a <portal to="destination"> tag, we get that error.

MrRossBennett avatar May 15 '19 11:05 MrRossBennett

@Haroenv : Here's the simple sandbox example:

https://codesandbox.io/s/zkm7kp66r3?fontsize=14

MrRossBennett avatar May 15 '19 12:05 MrRossBennett

Unfortunately it seems like because of the way Vue works, that portal elements won't work for Vue InstantSearch, because we rely on provide/inject to have the search data available on the correct nodes.

See https://portal-vue.linusb.org/guide/caveats.html#provide-inject

The only way I see around this is by styling with css to be on the spot where you want it, or for Vue to add native portals

Haroenv avatar May 15 '19 14:05 Haroenv

@MrRossBennett i just ran into the same issue.

I managed to work around it for now (still working out the edge cases and some weirdness), by wrapping a portal in a component that provides anything from a shared global bus (so it's reactive)

// ProviderComponent
  {
    created() {
      this.innerProviderComponent = {
        name: 'PortalProviderInner',
        provide: this.$bus.portalProviders,
        render(h) {
          return h('div', this.$slots.default)
        }
      }
    },
    render(h) {
      return h(this.innerProviderComponent, this.$slots.default)
    }
  } 

this.$bus is just a Vue instance attached to the prototype.

The last piece to this puzzle is a Dummy component that injects the instantSearchInstance and sets it on the $bus.

// Dummy
  {
    inject: ['instantSearchInstance'],
    render(h) {
      this.$set(this.$bus.portalProviders, 'instantSearchInstance',  this.instantSearchInstance)
      return h()
    }
  }

With these two, I have the Dummy as a child of the ais-instant-search component, and the ProviderComponent wraps the portal-target.

Another easier approach would be if you're able to wrap the portal-target in the same ais-instant-search component, so it can successfully provide for the widgets.

If you've figured out a different solution, please share - I'd like to know if there are better ways to work around it.

PS. Another Idea I've had, but haven't explored fully was to inject the instantSearchInstance into a component, and then pass it as a prop to another component that would be portal-ed, and re-provide the instantSearchInstance from it's props for the widgets that would be child elements.

rigor789 avatar Jul 15 '19 11:07 rigor789

For now I'd recommend either this workaround, or avoiding the need of portals when using Vue InstantSearch. If it's only a searchbox, you could update the URL or propagate an event down to call instantSearchInstance.setUiState(state => {state[myIndex].query = query; return state}) with the new query

Haroenv avatar Dec 21 '22 15:12 Haroenv