useAisWidget
useAisWidget(connectorName, widgetId?) returns the live render-state for a connector that the parent <AisInstantSearch> already mounted.
It powers every shipped widget component (<AisRefinementList>, <AisMenu>, etc.) and is the primary extension point when you build your own.
Signature
useAisWidget<TWidget extends keyof RenderState["string"]>(
widgetName: TWidget,
widgetId?: string,
): {
instance: Ref<InstantSearch>;
state: Ref<RenderState[string][TWidget]>;
widgetParams: ComputedRef<unknown>;
};
When to pass widgetId
- No
widgetId— the composable returns the connector's bucket for the current<AisIndex>scope. Use this when there is at most one instance of the connector per index (<AisHits>,<AisStats>, single<AisRefinementList>per attribute, etc.). widgetIdset — the composable reads the state from a registry that belongs to the InstantSearch instance. The key is${widgetName}-${indexScope}-${widgetId}. Each widget writes its state there on its first render, so a component can mount at any time, also after the first search. Use this when you mount the same connector more than once in the same index and each instance must keep separate state.
The id prop on the shipped components is forwarded straight to this argument.
How the state updates
useAisWidget does not watch the InstantSearch instance deeply. Until v1.3.2, each widget component ran watch(instance, …, { deep: true }). Vue then walked the full search instance (helper, results, all widgets) once per widget on each render. On a page with many widgets, this walk was the largest startup task (#59).
The composable now uses one of two update paths:
widgetIdset —stateis acomputedthat reads the widget's state ref from the registry. The connector writes a new render state into that ref on each render, and Vue sends the change straight to the component. No watcher on the instance is necessary.- No
widgetId—stateis set again frominstance.renderStatein awatchEffect. The module callstriggerRefon the instance after each render, and the effect runs again.
triggerRef gives the watcher the same instance object, so a shallow watch callback does not run. The deep option forced the callback to run. That is why { deep: false } alone is not a safe replacement for widgets without an id. The watchEffect runs again on each trigger, with no comparison and no traversal.Usage
<script setup lang="ts">
import { useAisWidget } from "#imports";
defineProps<{ id?: string }>();
const { state } = useAisWidget("hits", id);
</script>
<template>
<ul v-if="state">
<li v-for="hit in state.hits" :key="hit.objectID">
{{ hit.name }}
</li>
</ul>
</template>
Throwing behavior
If no widget is registered for the requested connector at mount time, useAisWidget throws:
Connector for component <name> not found, did you forget to add the proper widget?
In manual mode it usually means the matching useAis*({...}, id) call is missing from the :widgets array. In declarative mode the known causes are all surfaced at build time:
- the widget element was skipped by the transform (a
[nuxt-swiftsearch] skipped <Tag>build warning names the element and the reason), - the widget lives in a child component and
childWidgetDiscoveryis off, the component could not be resolved to a file, or it is a server component (see widgets inside child components).
Related
- Manual widgets mode — register connectors and widgets together.
- Declarative transform — automatic id injection.