> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Show and hide React InstantSearch widgets

> Learn how you can prevent common issues when showing and hiding React InstantSearch widgets.

export const SearchRequest = () => <Tooltip tip="A search request is a single HTTP call to the Algolia Search API that can run one or more search operations. It can include multiple queries, for example, when querying several indices at once.">
    search request
  </Tooltip>;

<Note>
  This is the **React InstantSearch v7** documentation.
  If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react/#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7).
  If you were using React InstantSearch Hooks,
  this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react/#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7).
  To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/).
</Note>

Widgets do more than display a UI: they each modify the [`uiState`](/doc/api-reference/widgets/ui-state/react) that maps to one or more Algolia search parameters.

For example,
the [`SearchBox`](/doc/api-reference/widgets/search-box/react) controls the [`query`](/doc/api-reference/api-parameters/query),
the [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) interacts with [`facetFilters`](/doc/api-reference/api-parameters/facetFilters), parameter.

**When a widget is mounted or unmounted, this is reflected in the InstantSearch UI state and included in the <SearchRequest />.**

Consider what happens in a mobile search interface with filters ([`RefinementList`](/doc/api-reference/widgets/refinement-list/react)): when a user clicks the **Filters** button, a dialog opens and displays the refinements.

<video controls>
  <source src="https://mintcdn.com/algolia/i8QBUoaIRr-QIxRo/videos/filters-dialog.webm?fit=max&auto=format&n=i8QBUoaIRr-QIxRo&q=85&s=0efbd3b1f13e3a5c4b6b4a9348f9c6a1" type="video/webm" data-path="videos/filters-dialog.webm" />

  <source src="https://mintcdn.com/algolia/i8QBUoaIRr-QIxRo/videos/filters-dialog.mp4?fit=max&auto=format&n=i8QBUoaIRr-QIxRo&q=85&s=212c19b55a23178d0b54fbbf6e896a33" type="video/mp4" data-path="videos/filters-dialog.mp4" />
</video>

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`](/doc/api-reference/widgets/refinement-list/react) 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:

* [Enable `preserveSharedStateOnUnmount`](#enable-preservesharedstateonunmount)
* [Keep the widget mounted but hidden](#keep-the-widget-mounted-but-hidden)
* [Persist the state on unmount](#persist-the-state-on-unmount) using a virtual widget
* [Use the Hook in a parent component](#use-the-hook-in-a-parent-component)

## Enable `preserveSharedStateOnUnmount`

Enabling [`preserveSharedStateOnUnmount`](/doc/api-reference/widgets/instantsearch/react#param-future-preserve-shared-state-on-unmount) 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.

<CodeGroup>
  ```jsx Search.jsx theme={"system"}
  import React, { useState } from "react";
  import { RefinementList, useRefinementList } from "react-instantsearch";

  export function Search() {
    useRefinementList({ attribute: "brand" });
    const [showMenu, setShowMenu] = useState(false);

    return (
      <>
        <button type="button" onClick={() => setShowMenu(!showMenu)}>
          Filters
        </button>
        {showMenu && <RefinementList attribute="brand" />}
        {/* ... */}
      </>
    );
  }
  ```

  ```jsx App.jsx theme={"system"}
  import React from "react";
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import { InstantSearch } from "react-instantsearch";

  import { Search } from "./Search";

  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"
        future={{
          preserveSharedStateOnUnmount: true,
        }}
      >
        <Search />
      </InstantSearch>
    );
  }
  ```
</CodeGroup>

<Note>
  This option is available from v7.2.0 and **will be the only option from React InstantSearch v8**.
</Note>

Find out more about this option in the [API reference](/doc/api-reference/widgets/instantsearch/react#param-future-preserve-shared-state-on-unmount).

## Keep the widget mounted but hidden

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:

<CodeGroup>
  ```jsx Dialog.jsx theme={"system"}
  import React from "react";

  export function Dialog({ open, children }) {
    return (
      <div
        style={{
          display: open ? "block" : "none",
        }}
      >
        {children}
      </div>
    );
  }
  ```

  ```jsx App.jsx theme={"system"}
  import React, { useState } from "react";
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import {
    CurrentRefinements,
    InstantSearch,
    RefinementList,
  } from "react-instantsearch";

  import { Dialog } from "./Dialog";

  const appID = "ALGOLIA_APPLICATION_ID";
  const apiKey = "ALGOLIA_SEARCH_API_KEY";

  const searchClient = algoliasearch(appID, apiKey);

  export function App() {
    const [dialogOpen, setDialogOpen] = useState(false);
    return (
      <InstantSearch searchClient={searchClient} indexName="instant_search">
        <CurrentRefinements />
        <button onClick={() => setDialogOpen(!dialogOpen)}>Filters</button>
        <Dialog open={dialogOpen}>
          <RefinementList attribute="brand" />
        </Dialog>
        {/* ... */}
      </InstantSearch>
    );
  }
  ```
</CodeGroup>

If you're using a component library, you can verify whether the dialog component lets you avoid unmounting its content. For example, the [`Dialog` component from Headless UI](https://headlessui.com/react/dialog) lets you turn off unmounting with the [`unmount` option](https://headlessui.com/react/dialog#dialog).

If you can't avoid unmounting, you can try [persisting the state on unmount](#persist-the-state-on-unmount).

## Persist the state on unmount

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.

<CodeGroup>
  ```jsx Filters.jsx theme={"system"}
  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;
  }
  ```

  ```jsx App.jsx theme={"system"}
  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>
    );
  }
  ```
</CodeGroup>

Two components are used: `Filters` and `VirtualFilters`.
`Filters` renders [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) and
[`RangeInput`](/doc/api-reference/widgets/range-input/react) 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`](/doc/api-reference/widgets/refinement-list/react#hook) and [`useRange`](/doc/api-reference/widgets/range-input/react#hook) 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.

{/* vale Google.Headings = NO */}

## Use the Hook in a parent component

In some situations you may want to use Hooks instead of widgets—for example,
if you're using [React Native](/doc/guides/building-search-ui/going-further/native/react),
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.

<CodeGroup>
  ```jsx Filters.jsx theme={"system"}
  import React, { useState } from "react";
  import { useRefinementList } from "react-instantsearch";

  import { Dialog } from "./Dialog";

  export function Filters() {
    const { items, refine } = useRefinementList({ attribute: "brand" });
    const [dialogOpen, setDialogOpen] = useState(false);

    return (
      <div>
        <button onClick={() => setDialogOpen(!dialogOpen)}>Filters</button>
        <Dialog open={dialogOpen}>
          <ul>
            {items.map((item) => (
              <li key={item.value}>
                <label>
                  <input
                    type="checkbox"
                    onChange={() => refine(item.value)}
                    checked={item.isRefined}
                  />
                  <span>{item.label}</span>
                </label>
              </li>
            ))}
          </ul>
        </Dialog>
      </div>
    );
  }
  ```

  ```jsx App.jsx theme={"system"}
  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>
    );
  }
  ```
</CodeGroup>

When using Hooks, you're in charge of [rendering the UI](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#customize-the-complete-ui-of-the-widgets) 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](#keep-the-widget-mounted-but-hidden) or [persisting the state on unmount instead](#persist-the-state-on-unmount).
