vue-instantsearch
vue-instantsearch copied to clipboard
Portal Vue + Vue InstantSearch Widgets
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!
Can you create what you have so far in a sandbox environment? We have a template available for that purpose here. Thanks!
Thanks, we'll do that.
Essentially though, if we wrap any vue-instantsearch widgets in a <portal to="destination"> tag, we get that error.
@Haroenv : Here's the simple sandbox example:
https://codesandbox.io/s/zkm7kp66r3?fontsize=14
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
@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.
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