Learn how you can prevent common issues when showing and hiding React InstantSearch widgets.
This is the React InstantSearch v7 documentation.
If you’re upgrading from v6, see the upgrade guide.
If you were using React InstantSearch Hooks,
this v7 documentation applies—just check for necessary changes.
To continue using v6, you can find the archived documentation.
Widgets do more than display a UI: they each modify the uiState that maps to one or more Algolia search parameters.For example,
the SearchBox controls the query,
the RefinementList interacts with facetFilters, parameter.When a widget is mounted or unmounted, this is reflected in the InstantSearch UI state and included in the .Consider what happens in a mobile search interface with filters (RefinementList): when a user clicks the Filters button, a dialog opens and displays the refinements.In many component libraries, the Dialog component mounts and unmounts its content when toggled. This is problematic when used with InstantSearch components.For example, if you have a RefinementList widget nested in the dialog:
The widget wouldn’t be mounted on the first app load because the dialog box is closed.
When the dialog box opens, the widget mounts, adding it to the InstantSearch state and triggering a new request, even before a refinement has been selected.
When the dialog box closes, the widget unmounts, removing it from the InstantSearch state and losing all selected refinements.
To keep the state available after unmount, you can either:
Enabling preserveSharedStateOnUnmount prevents a widget’s state from being cleared when it’s unmounted, as long as other widgets share the same refinements.
Enabling this option changes how dispose is used in the InstantSearch lifecycle.By default when a widget is removed, it provides a cleared version of the state that will be propagated throughout the other widgets.
The most straightforward way to retain a widget’s state and refinements is to avoid unmounting it.You can, for example, hide the content of the dialog with CSS:
import React from "react";export function Dialog({ open, children }) { return ( <div style={{ display: open ? "block" : "none", }} > {children} </div> );}
If you can’t prevent unmounting a widget, you can keep track of the InstantSearch UI state to preserve it and apply it back when the dialog unmounts.
import React, { useEffect, useRef } from "react";import { RangeInput, RefinementList, useInstantSearch, useRange, useRefinementList,} from "react-instantsearch";export function Filters() { const { uiState, setUiState } = useInstantSearch(); const uiStateRef = useRef(uiState); // Keep up to date uiState in a reference useEffect(() => { uiStateRef.current = uiState; }, [uiState]); // Apply latest uiState to InstantSearch as the component is unmounted useEffect(() => { return () => { setTimeout(() => setUiState(uiStateRef.current)); }; }, [setUiState]); return ( <div> <h2>Brands</h2> <RefinementList attribute="brand" /> <h2>Price range</h2> <RangeInput attribute="price" /> </div> );}export function VirtualFilters() { useRefinementList({ attribute: "brand" }); useRange({ attribute: "price" }); return null;}
import React, { useState } from "react";import { liteClient as algoliasearch } from "algoliasearch/lite";import { CurrentRefinements, InstantSearch } from "react-instantsearch";import { Filters, VirtualFilters } from "./Filters";import { Dialog } from "./Dialog";const searchClient = algoliasearch("undefined", "undefined");export function App() { const [dialogOpen, setDialogOpen] = useState(false); return ( <InstantSearch searchClient={searchClient} indexName="instant_search"> <CurrentRefinements /> <VirtualFilters /> <button onClick={() => setDialogOpen(!dialogOpen)}>Filters</button> <Dialog open={dialogOpen}> <Filters /> </Dialog> {/* ... */} </InstantSearch> );}
Two components are used: Filters and VirtualFilters.
Filters renders RefinementList and
RangeInput for the brand and price attributes.
This component is nested in Dialog (see App.jsx), so the widgets are mounted and unmounted as users toggle the dialog.To avoid losing applied filters, the VirtualFilters uses useRefinementList and useRange which register themselves in InstantSearch for the same brand and price attributes as the widgets.
The component is “renderless”, users don’t interact with it, but it allows persisting the state for brand and price within InstantSearch even when the widgets are unmounted.
In some situations you may want to use Hooks instead of widgets—for example,
if you’re using React Native,
or if you want to fully control what’s rendered.
In this case you can use Hooks in a parent component which isn’t subject to being mounted and unmounted.
import React from "react";import { liteClient as algoliasearch } from "algoliasearch/lite";import { CurrentRefinements, InstantSearch } from "react-instantsearch";import { Filters } from "./Filters";const appID = "ALGOLIA_APPLICATION_ID";const apiKey = "ALGOLIA_SEARCH_API_KEY";const searchClient = algoliasearch(appID, apiKey);export function App() { return ( <InstantSearch searchClient={searchClient} indexName="instant_search"> <CurrentRefinements /> <Filters /> </InstantSearch> );}
When using Hooks, you’re in charge of rendering the UI and setting up UI events. While this provides full control, it also requires more work on your end. If the only reason for you to use Hooks is to fix mounting and unmounting issues, it’s strongly recommended try hiding the widget or persisting the state on unmount instead.