Guides / Building Search UI / Upgrade guides

Upgrading React InstantSearch Hooks

Migrating from React InstantSearch to React InstantSearch Hooks

React InstantSearch Hooks and React InstantSearch are separate, incompatible libraries.

React InstantSearch is a standalone library with ready-to-use UI components. React InstantSearch Hooks is based on InstantSearch.js, and lets you fully control the rendering. With React InstantSearch Hooks, you can create your own UI components using React Hooks.

This guide helps you migrate from React InstantSearch v6 to React InstantSearch Hooks v6. If you’re using React InstantSearch < 6, please upgrade to v6 first.

Components

React InstantSearch Hooks provides most of the same UI components as React InstantSearch. When no UI component is available, you can use the matching Hooks to build it yourself.

React InstantSearch React InstantSearch Hooks
<Breadcrumb> <Breadcrumb>
<ClearRefinements> <ClearRefinements>
<Configure> <Configure>
<CurrentRefinements> <CurrentRefinements>
<DynamicWidgets> <DynamicWidgets>
<EXPERIMENTAL_Answers> useConnector(connectAnswers) (no UI component)
EXPERIMENTAL_useAnswers() useConnector(connectAnswers) (no UI component)
<ExperimentalConfigureRelatedItems> useConnector(connectConfigureRelatedItems) (no UI component)
<HierarchicalMenu> <HierarchicalMenu>
<Highlight> <Highlight>
<Hits> <Hits>
<HitsPerPage> <HitsPerPage>
<Index> <Index>
<InfiniteHits> <InfiniteHits>
<InstantSearch> <InstantSearch>
<Menu> <Menu>
<MenuSelect> useMenu() (no UI component)
<NumericMenu> useConnector(connectNumericMenu) (no UI component)
<Pagination> <Pagination>
<Panel> No equivalent available
<PoweredBy> <PoweredBy>
<QueryRuleContext> useQueryRules() (no UI component)
<QueryRuleCustomData> useQueryRules() (no UI component)
<RangeInput> <RangeInput>
<RangeSlider> useRange() (no UI component)
<RatingMenu> useMenu() (no UI component)
<RefinementList> <RefinementList>
<RelevantSort> useConnector(connectRelevantSort) (no UI component)
<ScrollTo> No equivalent available
<SearchBox> <SearchBox>
<Snippet> <Snippet>
<SortBy> <SortBy>
<Stats> No equivalent available
<ToggleRefinement> <ToggleRefinement>
<VoiceSearch> useConnector(connectVoiceSearch) (no UI component)

Component props differ slightly between React InstantSearch and React InstantSearch Hooks. Refer to the API reference of each component for information.

The <Menu> widget in React InstantSearch Hooks isn’t searchable. You can create a searchable version by using a custom widget with the useRefinementList() connector.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import {
  useRefinementList,
  useInstantSearch,
} from 'react-instantsearch-hooks-web';

function Menu({ attribute, ...props }) {
  const { setIndexUiState } = useInstantSearch();
  const { items, searchForItems, isFromSearch } = useRefinementList({
    attribute,
  });
  const [query, setQuery] = React.useState('');

  function refine(value: string) {
    setQuery('');
    setIndexUiState((uiState) => ({
      ...uiState,
      refinementList: {
        ...uiState.refinementList,
        [attribute]: [value],
      },
    }));
  }

  return (
    <div {...props}>
      <input
        type="search"
        value={query}
        onChange={(event) => {
          const nextValue = event.target.value;
          setQuery(nextValue);
          searchForItems(nextValue);
        }}
      />
      <ul>
        {items.map((item) => (
          <li key={item.label}>
            <label>
              <input
                type="radio"
                checked={item.isRefined}
                onChange={() => refine(item.value)}
              />
              {isFromSearch ? (
                <Highlight hit={mapToHit(item)} attribute="highlighted" />
              ) : (
                item.label
              )}
            </label>
          </li>
        ))}
      </ul>
    </div>
  );
}

function mapToHit(item: RefinementListItem): AlgoliaHit<RefinementListItem> {
  return {
    ...item,
    _highlightResult: {
      highlighted: {
        value: item.highlighted,
        matchLevel: 'full',
        matchedWords: [],
      },
    },
    __position: 0,
    objectID: '',
  };
}

Connectors

React InstantSearch Hooks provides the same connectors as React InstantSearch except the connectStateResults connector, which isn’t available in React InstantSearch Hooks.

connectStateResults

React InstantSearch Hooks doesn’t have the connectStateResults connector. You can use the useInstantSearch() hook instead.

Using connectors and hooks

React InstantSearch Hooks is a bridge between InstantSearch.js connectors and React Hooks. The connectors from React InstantSearch and InstantSearch.js were historically different APIs, meaning there are differences between the props that React InstantSearch connectors accept and the new Hooks.

Refer to the API reference to see the new props.

The React InstantSearch Hooks package uses TypeScript natively. If your editor supports code completion (IntelliSense), you can use it to discover the new props.

Creating connectors

The connector API has changed to use InstantSearch.js connectors. The previous createConnector() function is no longer available.

React InstantSearch Hooks works with all InstantSearch.js connectors—official Algolia connectors, and community ones.

To create your own Hook, you can use an existing connector or create your InstantSearch.js connector.

Routing

Routing now follows the InstantSearch.js routing APIs with the <InstantSearch> routing prop.

Server-side rendering (SSR)

The server APIs have been simplified in React InstantSearch Hooks.

Replace findResultsState() with getServerState(). This new API accepts an element <App />. You can pass props directly to <App />.

In your server code, you don’t need to provide your index name and your search client anymore, because they’re already in your <App>.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 import express from 'express';
 import React from 'react';
 import { renderToString } from 'react-dom/server';
-import { findResultsState } from 'react-instantsearch-dom/server';
+import { getServerState } from 'react-instantsearch-hooks-server';
 import App from './App';

-const indexName = 'instant_search';
-const searchClient = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');

 const app = express();

 app.get('/', async (req, res) => {
-  const searchState = { /* ... */ };
-  const serverState = {
-    resultsState: await findResultsState(App, {
-      searchClient,
-      indexName,
-      searchState,
-    }),
-    searchState,
-  };
+  const serverState = await getServerState(<App />);
   const html = renderToString(<App serverState={serverState} />);

   res.send(
     `
   <!DOCTYPE html>
   <html>
     <head>
       <script>window.__SERVER_STATE__ = ${JSON.stringify(serverState)};</script>
     </head>
     <body>
       <div id="root">${html}</div>
     </body>
     <script src="/assets/bundle.js"></script>
   </html>
     `
   );
 });

 app.listen(8080);

You don’t need to pass any props to <InstantSearch> to support SSR, only wrap the component to render on the server with <InstantSearchSSRProvider>. Then, pass the server state by spreading the getServerState() prop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 import algoliasearch from 'algoliasearch/lite';
 import React from 'react';
-import { InstantSearch } from 'react-instantsearch-dom';
+import {
+  InstantSearch,
+  InstantSearchSSRProvider,
+} from 'react-instantsearch-hooks-web';

 const searchClient = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');

 function App(props) {
   return (
+    <InstantSearchSSRProvider {...props.serverState}>
       <InstantSearch
         indexName="instant_search"
         searchClient={searchClient}
-        resultsState={props.serverState.resultsState}
-        onSearchParameters={props.onSearchParameters}
-        widgetsCollector={props.widgetsCollector}
       >
         {/* Widgets */}
       </InstantSearch>
+    </InstantSearchSSRProvider>
   );
 }

 export default App;

Types

Types are now available in React InstantSearch Hooks.

You can uninstall all InstantSearch types coming from DefinitelyTyped.

1
2
3
yarn remove @types/react-instantsearch-dom @types/react-instantsearch-core @types/react-instantsearch
# or
npm uninstall @types/react-instantsearch-dom @types/react-instantsearch-core @types/react-instantsearch

Default refinement

In React InstantSearch, every widget has a defaultRefinement prop. This doesn’t exist in React InstantSearch hooks and is replaced with initialUiState.

Controlled mode

The props searchState and onSearchStateChange are equivalent to the onStateChange prop.

Upgrade event tracking

Starting from v6.43.0, React InstantSearch Hooks simplifies the event tracking process via the insights option. You no longer need to install the search-insights library or set up the insights middleware yourself.

Here are some benefits when using the insights option:

  • It better handles the userToken. Once you set it, all the search and event tracking calls include the token.
  • It automatically sends default events from built-in widgets such as <RefinementList>, <Menu>, etc. You can also change the event payloads, or remove them altogether.
  • It lets you send custom events from your custom widgets.
  • It simplifies forwarding events to third-party trackers.

If you’ve been tracking events directly with search-insights or with the insights middleware, you should:

  1. Upgrade react-instantsearch-hooks to v6.43.0 or greater
  2. Migrate from using the insights middleware to the insights option
  3. Either update or remove the search-insights library

Use the insights option

Starting from v6.43.0, InstantSearch lets you enable event tracking with the insights option. You no longer need to set up the insights middleware yourself.

1
2
3
4
5
6
7
8
9
10
function App() {
  return (
    <InstantSearch
      // ...
      insights={true}
    >
      {/* Widgets */}
    </InstantSearch>
  );
}

If you had already set up the insights middleware in your code, you can now remove it and move its configuration to the insights option.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-import { createInsightsMiddleware } from 'instantsearch.js/es/middlewares';
-import { useInstantSearch } from 'react-instantsearch-hooks-web';
-import { useLayoutEffect } from 'react';
import algoliasearch from 'algoliasearch/lite'; 

- function InsightsMiddleware() {
-   const { use } = useInstantSearch();
- 
-   useLayoutEffect(() => {
-     const middleware = createInsightsMiddleware({
-       insightsClient: window.aa,
-       insightsInitParams: {
-         useCookie: false,
-       }
-     });
- 
-     return use(middleware);
-   }, [use]);
- 
-   return null;
-}

function App() {
  return (
    <InstantSearch
      // ...
+      insights={{
+        insightsInitParams: {
+          useCookie: false,
+        }
+      }}
    >
      <InsightsMiddleware />
      {/* Widgets */}
    </InstantSearch>
  );
}

Update search-insights

Starting from v4.55.0, InstantSearch can load search-insights for you so the insightsClient option is no longer required.

If you prefer loading it yourself, make sure to update search-insights to v2.4.0 and forward the reference to insights.

If you’re using the UMD bundle with a <script> tag, make sure to update the full code snippet (not just the version):

1
2
3
4
5
6
7
8
<script>
  var ALGOLIA_INSIGHTS_SRC = "https://cdn.jsdelivr.net/npm/search-insights@2.6.0/dist/search-insights.min.js";

  !function(e,a,t,n,s,i,c){e.AlgoliaAnalyticsObject=s,e[s]=e[s]||function(){
  (e[s].queue=e[s].queue||[]).push(arguments)},e[s].version=(n.match(/@([^\/]+)\/?/) || [])[1],i=a.createElement(t),c=a.getElementsByTagName(t)[0],
  i.async=1,i.src=n,c.parentNode.insertBefore(i,c)
  }(window,document,"script",ALGOLIA_INSIGHTS_SRC,"aa");
</script>

If you’re using a package manager, you can upgrade it to the latest version.

1
2
3
npm install search-insights
# or
yarn add search-insights
1
2
3
4
5
6
7
8
9
10
11
12
function App() {
  return (
    <InstantSearch 
      // ...
      insights={{
        insightsClient: window.aa,
      }
    >
      {/* ... */}
    </InstantSearch>
  );
}

Otherwise, you can remove it and let InstantSearch handle it for you.

Remove search-insights

Starting from v4.55.0, InstantSearch loads search-insights for you from jsDelivr if not detected in the page. If you’ve installed search-insights, you can now remove it.

If you’re using the UMD bundle with a <script> tag, you can remove the snippet:

1
2
3
4
5
6
7
8
- <script>
-   var ALGOLIA_INSIGHTS_SRC = "https://cdn.jsdelivr.net/npm/search-insights@2.6.0/dist/search-insights.min.js";
- 
-   !function(e,a,t,n,s,v,i,c){e.AlgoliaAnalyticsObject=s,e[s]=e[s]||function(){
-   (e[s].queue=e[s].queue||[]).push(arguments)},e[s].version=(n.match(/@([^\/]+)\/?/) || [])[1],i=a.createElement(t),c=a.- getElementsByTagName(t)[0],
-   i.async=1,i.src=n,c.parentNode.insertBefore(i,c)
-   }(window,document,"script",ALGOLIA_INSIGHTS_SRC,"aa");
- </script>

If you’re using a package manager, you can upgrade it to the latest version.

1
2
3
npm uninstall search-insights
# or
yarn remove search-insights

Then you can remove the reference to search-insights from your code:

1
2
3
4
5
6
7
8
9
10
11
12
function App() {
  return (
    <InstantSearch 
      // ...
      insights={{
-       insightsClient: window.aa,
      }
    >
      {/* ... */}
    </InstantSearch>
  );
}

InstantSearch loads search-insights from the jsDelivr CDN, which requires that your site or app allows script execution from foreign resources. Check the security best practices for recommendations.

Did you find this page helpful?