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

# React Native

> How to create a React InstantSearch app using React Native.

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 Application = () => <Tooltip tip="An Algolia application is a self-contained environment with its own indices, configuration, and API keys. Applications don't share data or settings with each other.">
    application
  </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>

**React InstantSearch is compatible with [React Native](https://reactnative.dev/)** but,
since its UI components are designed for the web, they don't work directly in React Native apps.
You can use [Hooks](/doc/guides/building-search-ui/what-is-instantsearch/react#use-hooks)
with the React Native [core components](https://reactnative.dev/docs/intro-react-native-components)
or any third-party React Native component library to incorporate InstantSearch features into your React Native app.

This guide covers how to integrate InstantSearch in your React Native app:

* Adding a search box to send queries

* Displaying infinite hits and highlighting matches

* Filtering in a modal to narrow down the results set

* [View source code](https://github.com/algolia/doc-code-samples/tree/master/react-instantsearch-native/getting-started)

## Before you begin

This tutorial assumes you have React and React Native knowledge and an existing React Native app with React ≥ [16.8.0](https://reactjs.org/blog/2019/02/06/react-v16.8.0.html) app.
If you don't have a React Native app, [create one](https://reactnative.dev/docs/environment-setup).

The tutorial also requires an [installation of `algoliasearch` and `react-instantsearch-core`](/doc/guides/building-search-ui/installation/react).

## Add InstantSearch to your app

The [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) wrapper connects your InstantSearch app to your Algolia <Application />.

```js App.js icon=code theme={"system"}
import React from "react";
import { SafeAreaView, StatusBar, StyleSheet, Text, View } from "react-native";
import { liteClient as algoliasearch } from "algoliasearch/lite";
import { InstantSearch } from "react-instantsearch-core";

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

export default function App() {
  return (
    <SafeAreaView style={styles.safe}>
      <StatusBar style="light" />
      <View style={styles.container}>
        <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
          {/* ... */}
        </InstantSearch>
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  safe: {
    flex: 1,
    backgroundColor: "#252b33",
  },
  container: {
    flex: 1,
    backgroundColor: "#ffffff",
    flexDirection: "column",
  },
});
```

Replace `INDEX_NAME` with the name of your <Index />.
Add styles using the [`StyleSheet`](https://reactnative.dev/docs/stylesheet) API from React Native.

## 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 [`useSearchBox`](/doc/api-reference/widgets/search-box/react#hook) Hook to display an Algolia search box.
Use it with the [`TextInput`](https://reactnative.dev/docs/textinput) React Native component.
Then, add the custom `SearchBox` component to your app.

<CodeGroup>
  ```jsx SearchBox.js theme={"system"}
  import React, { useRef, useState } from "react";
  import { StyleSheet, View, TextInput } from "react-native";
  import { useSearchBox } from "react-instantsearch-core";

  export function SearchBox(props) {
    const { query, refine } = useSearchBox(props);
    const [inputValue, setInputValue] = useState(query);
    const inputRef = useRef(null);

    function setQuery(newQuery) {
      setInputValue(newQuery);
      refine(newQuery);
    }

    // Track when the InstantSearch query changes to synchronize it with
    // the React state.
    // Bypass the state update if the input is focused to avoid concurrent
    // updates when typing.
    if (query !== inputValue && !inputRef.current?.isFocused()) {
      setInputValue(query);
    }

    return (
      <View style={styles.container}>
        <TextInput
          ref={inputRef}
          style={styles.input}
          value={inputValue}
          onChangeText={setQuery}
          clearButtonMode="while-editing"
          autoCapitalize="none"
          autoCorrect={false}
          spellCheck={false}
          autoComplete="off"
        />
      </View>
    );
  }

  const styles = StyleSheet.create({
    container: {
      backgroundColor: "#252b33",
      padding: 18,
    },
    input: {
      height: 48,
      padding: 12,
      fontSize: 16,
      backgroundColor: "#fff",
      borderRadius: 4,
      borderWidth: 1,
      borderColor: "#ddd",
    },
  });
  ```

  ```jsx App.js theme={"system"}
  // ...
  import { SearchBox } from "./SearchBox";

  // ...

  export default function App() {
    return (
      <SafeAreaView style={styles.safe}>
        <StatusBar style="light" />
        <View style={styles.container}>
          <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
            <SearchBox />
          </InstantSearch>
        </View>
      </SafeAreaView>
    );
  }

  // ...
  ```
</CodeGroup>

Replace `INDEX_NAME` with the name of your index.

## Display infinite hits

When Algolia returns results, you want to list them in the UI.
A common way of dealing with lists on mobile devices is to display an "infinite list":
a list that loads more results when users scroll to the bottom of the screen.

To do this, use the [`useInfiniteHits`](/doc/api-reference/widgets/infinite-hits/react#hook) Hook with the [`FlatList`](https://reactnative.dev/docs/flatlist) React Native component.
Then, add the custom `InfiniteHits` component in your app and pass it a custom `Hit` component to display each Algolia result.

<CodeGroup>
  ```jsx InfiniteHits.js theme={"system"}
  import React from "react";
  import { StyleSheet, View, FlatList } from "react-native";
  import { useInfiniteHits } from "react-instantsearch-core";

  export function InfiniteHits({ hitComponent: Hit, ...props }) {
    const { items, isLastPage, showMore } = useInfiniteHits({
      ...props,
      escapeHTML: false,
    });

    return (
      <FlatList
        data={items}
        keyExtractor={(item) => item.objectID}
        ItemSeparatorComponent={() => <View style={styles.separator} />}
        onEndReached={() => {
          if (!isLastPage) {
            showMore();
          }
        }}
        renderItem={({ item }) => (
          <View style={styles.item}>
            <Hit hit={item} />
          </View>
        )}
      />
    );
  }

  const styles = StyleSheet.create({
    separator: {
      borderBottomWidth: 1,
      borderColor: "#ddd",
    },
    item: {
      padding: 18,
    },
  });
  ```

  ```jsx App.js theme={"system"}
  // ...
  import { InfiniteHits } from "./InfiniteHits";

  // ...

  export default function App() {
    return (
      <SafeAreaView style={styles.safe}>
        <StatusBar style="light" />
        <View style={styles.container}>
          <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
            <SearchBox />
            <InfiniteHits hitComponent={Hit} />
          </InstantSearch>
        </View>
      </SafeAreaView>
    );
  }

  function Hit({ hit }) {
    return <Text>{hit.name}</Text>;
  }

  // ...
  ```
</CodeGroup>

Replace `INDEX_NAME` with the name of your index.

<Frame title="Load more results as users scroll">
  <img src="https://mintcdn.com/algolia/1WCtS02LoYjihIhK/images/instantsearch/react/native/infinite-hits.gif?s=1a48a680bb855d1883953343a3fed26c" alt="Screenshot of a mobile interface displaying a list of products with 'Load more results as users scroll' feature." width="360" height="778" data-path="images/instantsearch/react/native/infinite-hits.gif" />
</Frame>

The search box remains visible, so users can update their <SearchQuery /> without manually scrolling back up.
When users do this, reset the scroll position of the list so they can see the top results first.

To do this:

1. Assign a reference (`ref`) to the list for later use in your app.
2. Wrap the `InfiniteHits` component in [`forwardRef`](https://reactjs.org/docs/forwarding-refs.html). This lets you pass `ref` from the app to the list.
3. Listen for the query to change by waiting for an `onChange` callback on the custom `SearchBox`.
4. When the query changes, call [`FlatList.scrollToOffset`](https://reactnative.dev/docs/flatlist#scrolltooffset) to scroll the list to the correct position.

<CodeGroup>
  ```jsx InfiniteHits.js theme={"system"}
  import React, { forwardRef } from "react";
  // ...

  export const InfiniteHits = forwardRef(
    ({ hitComponent: Hit, ...props }, ref) => {
      // ...

      return (
        <FlatList
          ref={ref}
          // ...
        />
      );
    },
  );

  // ...
  ```

  ```jsx SearchBox.js theme={"system"}
  // ...

  export function SearchBox({ onChange, ...props }) {
    // ...

    return (
      <View style={styles.container}>
        <TextInput
          // ...
          onChangeText={(newValue) => {
            setQuery(newValue);
            onChange(newValue);
          }}
        />
      </View>
    );
  }

  // ...
  ```

  ```jsx App.js theme={"system"}
  import React, { useRef } from "react";
  import {
    FlatList,
    SafeAreaView,
    StatusBar,
    StyleSheet,
    Text,
    View,
  } from "react-native";

  // ...

  export default function App() {
    const listRef = useRef < FlatList > null;

    function scrollToTop() {
      listRef.current?.scrollToOffset({ animated: false, offset: 0 });
    }

    return (
      <SafeAreaView style={styles.safe}>
        <StatusBar style="light" />
        <View style={styles.container}>
          <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
            <SearchBox onChange={scrollToTop} />
            <InfiniteHits ref={listRef} hitComponent={Hit} />
          </InstantSearch>
        </View>
      </SafeAreaView>
    );
  }

  // ...
  ```
</CodeGroup>

## Highlight matches

Algolia supports highlighting and returns the highlighted parts of a result in the response.
Build a custom `Highlight` component to highlight matches in each attribute.

The `instantsearch.js` library provides the necessary utilities to parse the highlighted parts from the Algolia response.
React InstantSearch uses InstantSearch.js behind the scenes, so you don't need to include it as a separate dependency.

Use the [`Text`](https://reactnative.dev/docs/text) component from React Native to represent each highlighted and non-highlighted part.
Then, use the custom `Highlight` component in your custom `Hit` component.

<CodeGroup>
  ```js Highlight.js theme={"system"}
  import React, { Fragment } from "react";
  import { StyleSheet, Text } from "react-native";
  import {
    getHighlightedParts,
    getPropertyByPath,
  } from "instantsearch.js/es/lib/utils";

  function HighlightPart({ children, isHighlighted }) {
    return (
      <Text style={isHighlighted ? styles.highlighted : styles.nonHighlighted}>
        {children}
      </Text>
    );
  }

  export function Highlight({ hit, attribute, separator = ", " }) {
    const { value: attributeValue = "" } =
      getPropertyByPath(hit._highlightResult, attribute) || {};
    const parts = getHighlightedParts(attributeValue);

    return (
      <>
        {parts.map((part, partIndex) => {
          if (Array.isArray(part)) {
            const isLastPart = partIndex === parts.length - 1;

            return (
              <Fragment key={partIndex}>
                {part.map((subPart, subPartIndex) => (
                  <HighlightPart
                    key={subPartIndex}
                    isHighlighted={subPart.isHighlighted}
                  >
                    {subPart.value}
                  </HighlightPart>
                ))}

                {!isLastPart && separator}
              </Fragment>
            );
          }

          return (
            <HighlightPart key={partIndex} isHighlighted={part.isHighlighted}>
              {part.value}
            </HighlightPart>
          );
        })}
      </>
    );
  }

  const styles = StyleSheet.create({
    highlighted: {
      fontWeight: "bold",
      backgroundColor: "#f5df4d",
      color: "#6f6106",
    },
    nonHighlighted: {
      fontWeight: "normal",
      backgroundColor: "transparent",
      color: "black",
    },
  });
  ```

  ```js App.js theme={"system"}
  // ...
  import { Configure, InstantSearch } from "react-instantsearch-core";
  import { Highlight } from "./Highlight";

  export default function App() {
    // ...

    return (
      <SafeAreaView style={styles.safe}>
        <StatusBar style="light" />
        <View style={styles.container}>
          <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
            <Configure highlightPreTag="<mark>" highlightPostTag="</mark>" />
            <SearchBox onChange={scrollToTop} />
            <InfiniteHits ref={listRef} hitComponent={Hit} />
          </InstantSearch>
        </View>
      </SafeAreaView>
    );
  }

  // ...

  function Hit({ hit }) {
    return (
      <Text>
        <Highlight hit={hit} attribute="name" />
      </Text>
    );
  }

  // ...
  ```
</CodeGroup>

Replace `INDEX_NAME` with the name of your index.

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

<img src="https://mintcdn.com/algolia/1WCtS02LoYjihIhK/images/instantsearch/react/native/highlight.jpg?fit=max&auto=format&n=1WCtS02LoYjihIhK&q=85&s=9a999f4a294ee35db79184a7ae223627" alt="Screenshot of a search results page for 'apple', with matching terms highlighted in yellow in product listings." width="1284" height="2778" data-path="images/instantsearch/react/native/highlight.jpg" />

## 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 the [`useRefinementList`](/doc/api-reference/widgets/refinement-list/react#hook) Hook to <Filter /> items based on attributes such as brand,
size, or color.

Mobile devices have limited screen real estate.
Instead of displaying filters near the list of hits as you would on a desktop website,
mobile apps commonly set filters inside a modal.
With React Native, use the [`Modal`](https://reactnative.dev/docs/modal) component.

<CodeGroup>
  ```js Filters.js theme={"system"}
  import React from "react";
  import {
    Button,
    StyleSheet,
    SafeAreaView,
    Modal,
    Text,
    TouchableOpacity,
    View,
  } from "react-native";
  import {
    useClearRefinements,
    useCurrentRefinements,
    useRefinementList,
  } from "react-instantsearch-core";

  export function Filters({ isModalOpen, onToggleModal, onChange }) {
    const { items, refine } = useRefinementList({ attribute: "brand" });
    const { canRefine: canClear, refine: clear } = useClearRefinements();
    const { items: currentRefinements } = useCurrentRefinements();
    const totalRefinements = currentRefinements.reduce(
      (acc, { refinements }) => acc + refinements.length,
      0,
    );

    return (
      <>
        <TouchableOpacity style={styles.filtersButton} onPress={onToggleModal}>
          <Text style={styles.filtersButtonText}>Filters</Text>
          {totalRefinements > 0 && (
            <View style={styles.itemCount}>
              <Text style={styles.itemCountText}>{totalRefinements}</Text>
            </View>
          )}
        </TouchableOpacity>

        <Modal animationType="slide" visible={isModalOpen}>
          <SafeAreaView>
            <View style={styles.container}>
              <View style={styles.title}>
                <Text style={styles.titleText}>Brand</Text>
              </View>
              <View style={styles.list}>
                {items.map((item) => {
                  return (
                    <TouchableOpacity
                      key={item.value}
                      style={styles.item}
                      onPress={() => {
                        refine(item.value);
                        onChange();
                      }}
                    >
                      <Text
                        style={{
                          ...styles.labelText,
                          fontWeight: item.isRefined ? "800" : "400",
                        }}
                      >
                        {item.label}
                      </Text>
                      <View style={styles.itemCount}>
                        <Text style={styles.itemCountText}>{item.count}</Text>
                      </View>
                    </TouchableOpacity>
                  );
                })}
              </View>
            </View>
            <View style={styles.filterListButtonContainer}>
              <View style={styles.filterListButton}>
                <Button
                  title="Clear all"
                  color="#252b33"
                  disabled={!canClear}
                  onPress={() => {
                    clear();
                    onChange();
                    onToggleModal();
                  }}
                />
              </View>
              <View style={styles.filterListButton}>
                <Button
                  onPress={onToggleModal}
                  title="See results"
                  color="#252b33"
                />
              </View>
            </View>
          </SafeAreaView>
        </Modal>
      </>
    );
  }

  const styles = StyleSheet.create({
    container: {
      padding: 18,
      backgroundColor: "#ffffff",
    },
    title: {
      alignItems: "center",
    },
    titleText: {
      fontSize: 32,
    },
    list: {
      marginTop: 32,
    },
    item: {
      paddingVertical: 12,
      flexDirection: "row",
      justifyContent: "space-between",
      borderBottomWidth: 1,
      borderColor: "#ddd",
      alignItems: "center",
    },
    itemCount: {
      backgroundColor: "#252b33",
      borderRadius: 24,
      paddingVertical: 4,
      paddingHorizontal: 8,
      marginLeft: 4,
    },
    itemCountText: {
      color: "#ffffff",
      fontWeight: "800",
    },
    labelText: {
      fontSize: 16,
    },
    filterListButtonContainer: {
      flexDirection: "row",
    },
    filterListButton: {
      flex: 1,
      alignItems: "center",
      marginTop: 18,
    },
    filtersButton: {
      paddingVertical: 18,
      flexDirection: "row",
      justifyContent: "center",
      alignItems: "center",
    },
    filtersButtonText: {
      fontSize: 18,
      textAlign: "center",
    },
  });
  ```

  ```js App.js theme={"system"}
  import React, { useRef, useState } from "react";
  import { Filters } from "./Filters";

  // ...

  export default function App() {
    const [isModalOpen, setModalOpen] = useState(false);

    // ...

    return (
      <SafeAreaView style={styles.safe}>
        <StatusBar style="light" />
        <View style={styles.container}>
          <InstantSearch searchClient={searchClient} indexName="INDEX_NAME">
            <SearchBox onChange={scrollToTop} />
            <Filters
              isModalOpen={isModalOpen}
              onToggleModal={() => setModalOpen((isOpen) => !isOpen)}
              onChange={scrollToTop}
            />
            <InfiniteHits ref={listRef} hitComponent={Hit} />
          </InstantSearch>
        </View>
      </SafeAreaView>
    );
  }

  // ...
  ```
</CodeGroup>

Replace `INDEX_NAME` with the name of your index.
Replace the `brand` attributes with one of your index's [faceting attributes](/doc/guides/managing-results/refine-results/faceting/how-to/declaring-attributes-for-faceting-with-dashboard).

<img src="https://mintcdn.com/algolia/1WCtS02LoYjihIhK/images/instantsearch/react/native/filters-modal.gif?s=9c949e9d7e1dc37feca02deca227adad" alt="Screenshot of a mobile app filter list with 'phone' entered in the search box, highlighting 'Phone' in several product listings." width="360" height="778" data-path="images/instantsearch/react/native/filters-modal.gif" />

The **Filters** button reveals filters in a modal that slides from the bottom.
It also uses the [`useCurrentRefinements`](/doc/api-reference/widgets/current-refinements/react#hook) and [`useClearRefinements`](/doc/api-reference/widgets/clear-refinements/react#hook) Hooks to display how many refinements are applied and let users clear them all at once.

## Next steps

With this tutorial, you've got a solid starting point for building dynamic search interfaces.
Some steps to improve the app are:

* [Search in multiple indices](/doc/guides/building-search-ui/ui-and-ux-patterns/multi-index-search/react) with [`Index`](/doc/api-reference/widgets/index-widget/react).
* Use a different component library like [Lottie React Native](https://github.com/lottie-react-native/lottie-react-native) or [React Native Elements](https://reactnativeelements.com/).
* Discover more Hooks by checking the [API reference](/doc/api-reference/widgets/react).
