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

# Get started with React InstantSearch

> Use React InstantSearch to create a dynamic, filterable search UI.

export const SearchQuery = () => <Tooltip tip="The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.">
    search query
  </Tooltip>;

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

export const Filter = () => <Tooltip tip="A filter is a condition that limits which records Algolia returns. Filters often use one or more facet-value pairs, such as brand:Apple AND color:red. You can also filter by numeric values, dates, tags, booleans, or geographic constraints." cta="Filtering" href="/doc/guides/managing-results/refine-results/faceting">
    filter
  </Tooltip>;

export const FlavorSwitcher = ({current, baseHref = "", options = [], label = "InstantSearch framework"}) => {
  if (options.length === 0) {
    return <div className="not-prose" role="alert" style={{
      margin: "0.25rem 0 1.5rem",
      padding: "0.75rem",
      border: "1px solid #f59e0b",
      borderRadius: "0.625rem",
      color: "inherit",
      fontSize: "0.875rem"
    }}>
        FlavorSwitcher requires at least one option.
      </div>;
  }
  const selected = options.find(option => option.value === current) ?? options[0];
  return <div className="not-prose mint-flavor-switcher">
      <style>{`
        .mint-flavor-switcher {
          --mfs-bg: #ffffff;
          --mfs-bg-hover: #f4f4f5;
          --mfs-bg-current: #eef2ff;
          --mfs-border: #d4d4d8;
          --mfs-fg: #18181b;
          --mfs-muted: #71717a;
          --mfs-accent: #4f46e5;
          position: relative;
          width: min(100%, 19rem);
          margin: 0.25rem 0 1.5rem;
          color: var(--mfs-fg);
          font-size: 0.875rem;
          line-height: 1.25rem;
        }

        .dark .mint-flavor-switcher {
          --mfs-bg: #18181b;
          --mfs-bg-hover: #27272a;
          --mfs-bg-current: #272747;
          --mfs-border: #3f3f46;
          --mfs-fg: #fafafa;
          --mfs-muted: #a1a1aa;
          --mfs-accent: #a5b4fc;
        }

        .mint-flavor-switcher details {
          position: relative;
        }

        .mint-flavor-switcher summary {
          display: flex;
          min-height: 2.75rem;
          box-sizing: border-box;
          align-items: center;
          justify-content: space-between;
          gap: 0.75rem;
          padding: 0.625rem 0.75rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          color: var(--mfs-fg);
          cursor: pointer;
          font-weight: 600;
          list-style: none;
          transition: border-color 150ms ease, box-shadow 150ms ease;
        }

        .mint-flavor-switcher summary::-webkit-details-marker {
          display: none;
        }

        .mint-flavor-switcher summary:hover {
          border-color: var(--mfs-accent);
        }

        .mint-flavor-switcher summary:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: 2px;
        }

        .mint-flavor-switcher__label {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }

        .mint-flavor-switcher__chevron {
          flex: none;
          transition: transform 150ms ease;
        }

        .mint-flavor-switcher details[open] .mint-flavor-switcher__chevron {
          transform: rotate(180deg);
        }

        .mint-flavor-switcher__menu {
          position: absolute;
          z-index: 50;
          top: calc(100% + 0.375rem);
          left: 0;
          width: 100%;
          box-sizing: border-box;
          margin: 0;
          padding: 0.375rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          box-shadow: 0 12px 30px rgb(0 0 0 / 16%);
          list-style: none;
        }

        .mint-flavor-switcher__menu li {
          margin: 0;
          padding: 0;
        }

        .mint-flavor-switcher__option {
          display: grid;
          gap: 0.125rem;
          padding: 0.625rem 0.75rem;
          border-radius: 0.4rem;
          color: var(--mfs-fg);
          text-decoration: none;
        }

        .mint-flavor-switcher__option:hover {
          background: var(--mfs-bg-hover);
        }

        .mint-flavor-switcher__option:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: -2px;
        }

        .mint-flavor-switcher__option[aria-current="page"] {
          background: var(--mfs-bg-current);
          color: var(--mfs-accent);
        }

        .mint-flavor-switcher__name {
          font-weight: 600;
        }

        .mint-flavor-switcher__description {
          color: var(--mfs-muted);
          font-size: 0.8125rem;
        }

        @media (prefers-reduced-motion: reduce) {
          .mint-flavor-switcher summary,
          .mint-flavor-switcher__chevron {
            transition: none;
          }
        }
      `}</style>

      <details>
        <summary aria-label={`${label}: ${selected.label}`}>
          <span className="mint-flavor-switcher__label">{selected.label}</span>
          <svg className="mint-flavor-switcher__chevron" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="m6 9 6 6 6-6" />
          </svg>
        </summary>

        <ul className="mint-flavor-switcher__menu" aria-label={label}>
          {options.map(option => {
    const isCurrent = option.value === selected.value;
    const href = option.href ?? `${baseHref.replace(/\/$/, "")}/${encodeURIComponent(option.value)}`;
    return <li key={option.value}>
                <a className="mint-flavor-switcher__option" href={href} aria-current={isCurrent ? "page" : undefined}>
                  <span className="mint-flavor-switcher__name">
                    {option.label}
                  </span>
                  {option.description ? <span className="mint-flavor-switcher__description">
                      {option.description}
                    </span> : null}
                </a>
              </li>;
  })}
        </ul>
      </details>
    </div>;
};

<div className="mint-flavor-switcher-slot not-prose">
  <FlavorSwitcher
    current="react"
    baseHref="/doc/guides/building-search-ui/getting-started"
    options={[
{ value: "js", label: "JavaScript", description: "InstantSearch.js" },
{ value: "react", label: "React", description: "React InstantSearch" },
{ value: "vue", label: "Vue", description: "Vue InstantSearch" },
{ value: "ios", label: "iOS", description: "InstantSearch iOS" },
{ value: "android", label: "Android", description: "InstantSearch Android" },
{ value: "flutter", label: "Flutter", description: "Algolia for Flutter" },
]}
  />
</div>

<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>

## Before you begin

This quickstart requires:

* Basic React knowledge.
* A React ≥ [16.8.0](https://reactjs.org/blog/2019/02/06/react-v16.8.0.html) app. If you don't have a React app, use this [CodeSandbox template](https://codesandbox.io/s/react-new).
* An Algolia account. If you don't have one, [create a new account](https://www.algolia.com/users/sign_up) for free.
* An Algolia <Index /> with `image`, `categories`, `name`, `brand`, and `price` attributes. If you don't have one, use [a sample dataset](https://github.com/algolia/datasets/blob/master/ecommerce/records.json) and [send it to Algolia](/doc/guides/sending-and-managing-data/send-and-update-your-data).
* [`algoliasearch` and `react-instantsearch`](/doc/guides/building-search-ui/installation/react) installed.

## Quickstart

In this quickstart, you'll add an Algolia search interface to some starter code.
The code:

* Displays and formats a search box and results
* Uses InstantSearch's pre-built UI components (widgets) to filter results

<Steps>
  <Step title="Add a search box">
    The main UI component of a search experience is a search box.
    It's often how users discover content in your app.

    React InstantSearch provides a [`SearchBox`](/doc/api-reference/widgets/search-box/react) widget to display an Algolia search box.

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

    const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY");

    function App() {
      return (
        <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
          <SearchBox />
        </InstantSearch>
      );
    }
    ```

    <Check>
      Replace `INDEX_NAME` with the name of your index.
    </Check>

    If you type in the search box, you won't see any results until you add a `hits` widget to display them.
  </Step>

  <Step title="Display search results">
    Display the results returned by Algolia using the [`Hits`](/doc/api-reference/widgets/hits/react) widget.

    ```diff App.jsx icon=code theme={"system"}
    import { liteClient as algoliasearch } from 'algoliasearch/lite';
    -import { InstantSearch, SearchBox } from 'react-instantsearch';
    +import { InstantSearch, SearchBox, Hits } from 'react-instantsearch';

    const searchClient = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_SEARCH_API_KEY');

    +function Hit({ hit }) {
    +  return (
    +    <article>
    +      <img src={hit.image} alt={hit.name} />
    +      <p>{hit.categories[0]}</p>
    +      <h1>{hit.name}</h1>
    +      <p>${hit.price}</p>
    +    </article>
    +  );
    +}

    function App() {
      return (
        <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
          <SearchBox />
    +      <Hits hitComponent={Hit} />
        </InstantSearch>
      );
    }
    ```

    <Check>
      Replace `INDEX_NAME` with the name of your index.
    </Check>

    The `Hits` widget updates whenever you type a character in the search box.

    <img src="https://mintcdn.com/algolia/WOi5v-PGZrMZ2rOj/images/build-search-ui/hits-without-highlighting.png?fit=max&auto=format&n=WOi5v-PGZrMZ2rOj&q=85&s=f178f5467d9038091bde00ed2a741fd6" alt="Screenshot of a search for 'magic mouse' showing Apple products under 'COMPUTERS and TABLETS'." width="756" height="632" data-path="images/build-search-ui/hits-without-highlighting.png" />
  </Step>

  <Step title="Highlight matches">
    It's not always clear why one result ranks above another.
    To help make things more evident to users, **you can highlight the parts of a result that match the <SearchQuery />.**

    Algolia supports highlighting and returns the highlighted parts of a result in the response.
    Use the [`Highlight`](/doc/api-reference/widgets/highlight/react) widget to highlight matches in each attribute.

    ```diff App.jsx icon=code theme={"system"}
    import { liteClient as algoliasearch } from 'algoliasearch/lite';
    -import { InstantSearch, SearchBox, Hits } from 'react-instantsearch';
    +import {
    +  InstantSearch,
    +  SearchBox,
    +  Hits,
    +  Highlight,
    +} from 'react-instantsearch';

    const searchClient = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_SEARCH_API_KEY');

    function Hit({ hit }) {
      return (
        <article>
          <img src={hit.image} alt={hit.name} />
          <p>{hit.categories[0]}</p>
    -      <h1>{hit.name}</h1>
    +      <h1>
    +        <Highlight attribute="name" hit={hit} />
    +      </h1>
          <p>${hit.price}</p>
        </article>
      );
    }
    ```

    When users type a query, the UI highlights matches in each search result.

    <img src="https://mintcdn.com/algolia/WOi5v-PGZrMZ2rOj/images/build-search-ui/hits-with-highlighting.png?fit=max&auto=format&n=WOi5v-PGZrMZ2rOj&q=85&s=c848322fbcbf0fb7fbb1548dde309791" alt="Search box results with query highlighting in the quickstart app" width="760" height="634" data-path="images/build-search-ui/hits-with-highlighting.png" />
  </Step>

  <Step title="Send click and conversion events">
    To send [click and conversion events](/doc/guides/sending-events/getting-started) when users interact with your search UI,
    set the [`insights`](/doc/api-reference/widgets/instantsearch/react#param-insights) option to `true`.

    ```diff App.jsx icon=code theme={"system"}
    import { liteClient as algoliasearch } from 'algoliasearch/lite';
    import {
      InstantSearch,
      SearchBox,
      Hits,
      Highlight,
    } from 'react-instantsearch';

    const searchClient = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_SEARCH_API_KEY');

    function Hit({ hit }) {
      return (
        <article>
          <img src={hit.image} alt={hit.name} />
          <p>{hit.categories[0]}</p>
          <h1>
            <Highlight attribute="name" hit={hit} />
          </h1>
          <p>${hit.price}</p>
        </article>
      );
    }

    function App() {
      return (
    -     <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
    +     <InstantSearch searchClient={searchClient} indexName="INDEX_NAME" insights>
          <SearchBox />
          <Hits hitComponent={Hit} />
        </InstantSearch>
      );
    }
    ```
  </Step>

  <Step title="Add a filter">
    A search box is a great way to refine a search experience,
    but sometimes users need to narrow down the results to find what they're looking for in a specific category.
    Use [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) to <Filter /> items based on attributes such as brand, size, or color.

    ```diff App.jsx icon=code theme={"system"}
    import { liteClient as algoliasearch } from 'algoliasearch/lite';
    import {
      InstantSearch,
      SearchBox,
      Hits,
      Highlight,
    + RefinementList,
    } from 'react-instantsearch';

    const searchClient = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_SEARCH_API_KEY');

    function Hit({ hit }) {
      return (
        <article>
          <img src={hit.image} alt={hit.name} />
          <p>{hit.categories[0]}</p>
          <h1>
            <Highlight attribute="name" hit={hit} />
          </h1>
          <p>${hit.price}</p>
        </article>
      );
    }

    function App() {
      return (
        <InstantSearch searchClient={searchClient} indexName="INDEX_NAME" insights>
          <SearchBox />
    +     <RefinementList attribute="brand" />
          <Hits hitComponent={Hit} />
        </InstantSearch>
      );
    }
    ```

    <Check>
      Replace `INDEX_NAME` with the name of your index.
    </Check>
  </Step>

  <Step title="Paginate your results">
    The app only shows 20 hits but Algolia returns more results.

    <img src="https://mintcdn.com/algolia/WOi5v-PGZrMZ2rOj/images/build-search-ui/network-tab-nbHits.png?fit=max&auto=format&n=WOi5v-PGZrMZ2rOj&q=85&s=896f204f8f71cddce1b866a0edfaa038" alt="The browser Network tab shows the number of hits and number of pages" width="723" height="114" data-path="images/build-search-ui/network-tab-nbHits.png" />

    Use the [`Pagination`](/doc/api-reference/widgets/pagination/react) widget to navigate the pages.

    ```diff App.jsx icon=code theme={"system"}
    import { liteClient as algoliasearch } from 'algoliasearch/lite';
    import {
      InstantSearch,
      SearchBox,
      Hits,
      Highlight,
      RefinementList,
    + Pagination,
    } from 'react-instantsearch';

    const searchClient = algoliasearch('ALGOLIA_APPLICATION_ID', 'ALGOLIA_SEARCH_API_KEY');

    // ...

    function App() {
      return (
        <InstantSearch searchClient={searchClient} indexName="INDEX_NAME" insights>
          <SearchBox />
          <RefinementList attribute="brand" />
          <Hits hitComponent={Hit} />
    +     <Pagination />
        </InstantSearch>
      );
    }
    ```
  </Step>

  <Step title="Configure search parameters">
    You can override any [search parameters](/doc/api-reference/search-api-parameters) with [`Configure`](/doc/api-reference/widgets/configure/react).
    This widget doesn't render anything but instead instructs InstantSearch to send custom search parameters to Algolia.

    This update sets 40 hits per page.

    ```diff App.jsx icon=code theme={"system"}
    import { liteClient as algoliasearch } from 'algoliasearch/lite';
    import {
      InstantSearch,
      SearchBox,
      Hits,
      Highlight,
      RefinementList,
      Pagination,
    + Configure,
    } from 'react-instantsearch';

    // ...

    function App() {
      return (
        <InstantSearch searchClient={searchClient} indexName="INDEX_NAME" insights>
    +     <Configure hitsPerPage={40} />
          <SearchBox />
          <RefinementList attribute="brand" />
          <Hits hitComponent={Hit} />
          <Pagination />
        </InstantSearch>
      );
    }
    ```
  </Step>
</Steps>

## Next steps

With this quickstart, you've got a solid starting point for building dynamic search interfaces.
To enhance it:

* Use your own data.
* Load hits without pagination using [`InfiniteHits`](/doc/api-reference/widgets/infinite-hits/react).
* [Search more than one index](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/react) with [`Index`](/doc/api-reference/widgets/index-widget/react).
* [Server-side render](/doc/guides/building-search-ui/going-further/server-side-rendering/react) your app for increased performance.
* Provide some more refinement widgets. For example:

  * [`Menu`](/doc/api-reference/widgets/menu/react) to refine on a single value per attribute
  * [`HierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/react) to filter a hierarchy of categories
  * [`ToggleRefinement`](/doc/api-reference/widgets/toggle-refinement/react) to filter on and off on a given attribute value

  If you have several refinement widgets,
  use [`CurrentRefinements`](/doc/api-reference/widgets/current-refinements/react) to display the active filters and let users remove them individually.

## See also

* [What is InstantSearch?](/doc/guides/building-search-ui/what-is-instantsearch/react)
* [InstantSearch widgets](/doc/api-reference/widgets/react)
* [Send click and conversion events](/doc/guides/building-search-ui/events/react)
