InstantSearch / React / V6 / Guides

Customize a React InstantSearch Widget

Deprecated content
This documentation is for a deprecated version of React InstantSearch. Some features and settings may be missing or their usage may have changed. Refer to the documentation for the latest version of React InstantSearch for up-to-date information.

Highlight and snippet your search results

Search is all about helping users understand the results. This is especially true when using text-based search. When a user types a query in the search box, the results must show why the results match the query. That’s why Algolia implements robust highlighting that lets you display the matching parts of text attributes in the results. On top of that, Algolia implements snippeting to get only the meaningful part of a text when attributes have a lot of content.

This feature is already packaged for you in React InstantSearch, and like most of its features, it comes in two flavors, depending on your use case:

  • When using the DOM, you should use widgets
  • When using another rendering (such as React Native), use the connector

Highlight and snippet

Highlighting is based on the results and you need to customize the hits widget to use the Highlighter. The highlight and the snippet widgets take two props:

  • attribute: the path to the highlighted attribute of the hit (which can be either a string or an array of strings)
  • hit: a single result object

Notes

  • Use the highlight widget when displaying the regular value of an attribute.
  • Use the snippet widget when displaying the snippet version of an attribute. To use this widget, the attribute name passed to the attribute prop must be present in “Attributes to snippet” on the Algolia dashboard or configured as attributesToSnippet through a set settings call to the Algolia API.

In this example, a custom hit widget is created for results that have a highlighted name field. These examples use the mark tag to highlight. This is a tag specially made for highlighting pieces of text. The default tag is em, primarily for legacy reasons.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { InstantSearch, SearchBox, Hits, Highlight } from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const Hit = ({ hit }) => (
  <p>
    <Highlight attribute="name" hit={hit} tagName="mark" />
  </p>
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <SearchBox />
    <Hits hitComponent={Hit} />
  </InstantSearch>
);

export default App;

connectHighlight

The connector provides a function to extract the highlighting data from the results. This function takes a single parameter object with three properties:

  • attribute: the highlighted attribute name
  • hit: a single result object
  • highlightProperty: the path to the structure containing the highlighted attribute. The value is either _highlightResult or _snippetResult, depending on whether you want to make a Highlight or a Snippet widget.

Since those parameters are taken from the context in which the custom component is used, it’s reasonable to have them as props.

Here is an example of a custom Highlight widget. It can be used the same way as the widgets.

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
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { InstantSearch, SearchBox, Hits, connectHighlight } from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const CustomHighlight = connectHighlight(({ highlight, attribute, hit }) => {
  const parsedHit = highlight({
    highlightProperty: '_highlightResult',
    attribute,
    hit
  });

  return (
    <div>
      {parsedHit.map(
        part => (part.isHighlighted ? <mark>{part.value}</mark> : part.value)
      )}
    </div>
  );
});

const Hit = ({ hit }) => (
  <p>
    <CustomHighlight attribute="name" hit={hit} />
  </p>
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <SearchBox />
    <Hits hitComponent={Hit} />
  </InstantSearch>
);

Style your widgets

All widgets under the react-instantsearch-dom namespace are shipped with fixed CSS class names.

The format for those class names is ais-NameOfWidget-element--modifier. This follows the naming convention defined by SUIT CSS.

The different class names used by each widget are described on their respective documentation pages. You can also inspect the underlying DOM and style accordingly.

Loading the theme

No CSS is loaded into your page automatically, but there are two themes that you can load manually:

  • reset.css
  • satellite.css

It’s strongly recommended that you at least use reset.css to avoid visual side effects caused by the HTML semantics.

The reset theme CSS is included within the satellite CSS, so there is no need to import it separately when using the satellite theme.

Via CDN

The themes are available on jsDelivr:

Unminified:

Minified:

You can either copy paste the content into your own app or use a direct link to jsDelivr:

1
2
3
4
<!-- Include only the reset -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@8.5.1/themes/reset-min.css" integrity="sha256-KvFgFCzgqSErAPu6y9gz/AhZAvzK48VJASu3DpNLCEQ=" crossorigin="anonymous">
<!-- or include the full Satellite theme -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@8.5.1/themes/satellite-min.css" integrity="sha256-woeV7a4SRDsjDc395qjBJ4+ZhDdFn8AqswN1rlTO64E=" crossorigin="anonymous">

Using npm and webpack

1
2
npm install instantsearch.css
npm install --save-dev style-loader css-loader
1
2
3
4
// Include only the reset
import 'instantsearch.css/themes/reset.css';
// or include the full Satellite theme
import 'instantsearch.css/themes/satellite.css';
1
2
3
4
5
6
7
8
9
10
module.exports = {
  module: {
    loaders: [
      {
        test: /\.css$/,
        loaders: ['style?insertAt=top', 'css'],
      },
    ],
  },
};

Other bundlers

You can use any other module bundler like Browserify or Parcel to load Algolia’s CSS. React InstantSearch doesn’t rely on any specific module bundler or module loader.

Styling icons

You can style the icon colors using the widget class names:

1
2
3
4
.ais-SearchBox-submitIcon path,
.ais-SearchBox-resetIcon path {
  fill: red,
}

Translate your widgets

All static text rendered by widgets, such as “Load more”, “Show more” are translatable using the translations prop on relevant widgets.

This prop is a mapping of keys-to-translation values. Translation values can be either a string or a function, as some take parameters.

The different translation keys supported by widgets and their optional parameters are described on their respective documentation page.

With a string

Here’s an example configuring the “Show more” label with a string on a <Menu>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { InstantSearch, Menu } from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <Menu
      attribute="categories"
      showMore={true}
      translations={{
        showMore: 'Voir plus'
      }}
    />
  </InstantSearch>
);

With a function

Here’s an example configuring the “Show more” label with a function on a <Menu>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { InstantSearch, Menu } from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <Menu
      attribute="categories"
      showMore={true}
      translations={{
        showMore(extended) {
          return extended ? 'Voir moins' : 'Voir plus';
        }
      }}
    />
  </InstantSearch>
);

Modify the list of items in widgets

Every widget and connector that handles a list of items exposes a transformItems option. This option is a function that takes the items as a parameter and expects to return the items. You can use this option to sort, filter, and add manual values.

Sorting

This example uses the transformItems option to order the items by label in a ascending mode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { orderBy } from 'lodash';
import {
  InstantSearch,
  SearchBox,
  RefinementList,
} from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <SearchBox />
    <RefinementList
      attribute="categories"
      transformItems={items => orderBy(items, "label", "asc")}
    />
  </InstantSearch>
);

Filtering

This example uses the transformItems option to filter out items when the count is lower than 150

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import {
  InstantSearch,
  SearchBox,
  RefinementList,
} from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <SearchBox />
    <RefinementList
      attribute="categories"
      transformItems={items =>
        items.filter(item => item.count >= 150)
      }
    />
  </InstantSearch>
);

Add manual values

By default, the values in a RefinementList or a Menu are dynamic. This means that the values are updated with the context of the search. This is the expected behavior most of the time, but sometimes you may want to have a static list of values that never change. To achieve this, you can use the connectors.

This example uses the useRefinementList()connector to display a static list of values. This RefinementList will always display and only display the items “iPad” and “Printers”.

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
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import {
  InstantSearch,
  SearchBox,
  connectRefinementList
} from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <SearchBox />
    <StaticRefinementList
      attribute="categories"
      values={[
        { label: 'iPad', value: 'iPad' },
        { label: 'iPhone', value: 'iPhone' },
      ]}
    />
  </InstantSearch>
);

const StaticRefinementList = connectRefinementList(
  ({ values, currentRefinement, items, refine }) => (
    <ul className="ais-RefinementList-list">
      {values.map(staticItem => {
        const { isRefined } = items.find(
          item => item.label === staticItem.label
        ) || {
          isRefined: false,
        };

        return (
          <li key={staticItem.value}>
            <label>
              <input
                type="checkbox"
                value={staticItem.value}
                checked={isRefined}
                onChange={event => {
                  const value = event.currentTarget.value;
                  const next = currentRefinement.includes(value)
                    ? currentRefinement.filter(current => current !== value)
                    : currentRefinement.concat(value);

                  refine(next);
                }}
              />
              {staticItem.label}
            </label>
          </li>
        );
      })}
    </ul>
  )
);

Display facets with no matches

Hiding facets when they don’t match a query can be counter-intuitive. However, because of how Algolia handles faceting, you have to rely on workarounds on the frontend to display facets with no hits.

One way of displaying facets with no matches is by caching the results the first time you receive them. Then, if the amount of actual facet hits that Algolia returns is below the limit set, you can append the cached facets to the list.

This solution comes with limitations:

  • Facet hits coming from a faceted search (“Search for facet values”) can’t work because Algolia doesn’t return facets that don’t match (the highlighting won’t work on cached items).
  • Sorting might need to be applied again in the custom widget because the internal sorting happens before the renderer is called.
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
function uniqBy(items, property) {
  const seen = {};

  return items.filter(item => {
    const val = item[property];

    if (seen[val]) {
      return false;
    } else {
      seen[val] = true;
      return true;
    }
  });
}

const indexName = 'instant_search';
const brandAttribute = 'brand';
const brandLimit = 10;
const initialFacets = [];

// On the first load, we store all facets returned by Algolia
// so we can append them to the returned facets when Algolia returns enough.
// We set their `count` to 0 because they'll show up when nothing
// else matches.
// Note that this triggers another query.
searchClient
  .searchForFacetValues([
    {
      indexName,
      params: {
        facetName: brandAttribute,
        facetQuery: '',
        maxFacetHits: brandLimit,
      },
    },
  ])
  .then(([{ facetHits }]) => {
    initialFacets.push(
      ...facetHits.map(facet => ({
        ...facet,
        label: facet.value,
        value: facet.value,
        isRefined: false,
        count: 0,
      }))
    );
  });

// If Algolia doesn't return enough results, we lose track of a
// potentially refined facet.
// For example, if you refine on "Apple", then search for "chromecast",
// "Apple" is no longer returned, and we don't know that it was selected
// based on the initial facets.
// We need to keep track of the last state to reflect the fact that it
// was refined in the UI.
const currentRefinement = new Set();
const CustomRefinementList = connectRefinementList(({ items, refine }) => {
  const toggle = item => {  // update `currentRefinement` on toggle
    currentRefinement.clear();
    item.value.forEach(value => currentRefinement.add(value));
    // didn't use new Set([...]) because IE 11 doesn't support it.
    // ref: http://kangax.github.io/compat-table/es6/#test-Set
    refine(item.value);
  };

  // If a cached facet is already returned by Algolia, we want it to be
  // displayed rather than to display its cached value.
  // You might need to sort the items again here because the internal
  // sorting already happens before `items` is provided by `connectRefinementList`.
  const combinedItems = uniqBy([...items, ...initialFacets], 'label')
    .slice(0, brandLimit)
    .map(item => {
      // Here we re-calculate `isRefined` and `value` for each item
      // based on `currentRefinement` which we cached at `toggle`.
      const isRefined = currentRefinement.has(item.label);
      const value =
        isRefined === false
          ? [...currentRefinement, item.label]
          : [...currentRefinement].filter(label => label !== item.label);

      return {
        ...item,
        isRefined,
        value,
      };
    });

  return (
    <div>
      <ul>
        {combinedItems.map((item, index) => (
          <li key={index}>
            <label>
              <input
                type="checkbox"
                checked={item.isRefined}
                onChange={() => toggle(item)}
              />
              <span>
                {item.label}: {item.count}
              </span>
            </label>
          </li>
        ))}
      </ul>
    </div>
  );
});

const App = () => (
  <InstantSearch indexName={indexName} searchClient={searchClient}>
    <CustomRefinementList attribute="brand" />
  </InstantSearch>
);

Searching long lists

For some cases, you want to be able to directly search into a list of facet values. This can be achieved using the searchable prop on widgets like refinement-list, menu, or on connectors useRefinementList() and useMenu(). To enable this feature, you’ll need to make the attribute searchable using the API or the Dashboard).

With widgets

Use the searchable prop to add a search box to supported widgets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import {
  InstantSearch,
  SearchBox,
  RefinementList
} from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <RefinementList
      attribute="brand"
      searchable={true}
    />
  </InstantSearch>
);

With connectors

You can implement your own search box for searching for items in lists when using supported connectors by using those provided props:

  • searchForItems(query): call this function with a search query to trigger a new search for items
  • isFromSearch: true when you are in search mode and the provided items are search items results
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
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import {
  InstantSearch,
  Highlight,
  connectRefinementList
} from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);


const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <RefinementListWithSearchBox attribute="brand" />
  </InstantSearch>
);

const RefinementListWithSearchBox = connectRefinementList(
  ({ items, refine, searchForItems }) => {
    const values = items.map(item => {
      const label = item._highlightResult ? (
        <Highlight attribute="label" hit={item} />
      ) : (
        item.label
      );

      return (
        <li key={item.value}>
          <span onClick={() => refine(item.value)}>
            {label} {item.isRefined ? "- selected" : ""}
          </span>
        </li>
      );
    });

    return (
      <div>
        <input
          type="input"
          onChange={e => searchForItems(e.currentTarget.value)}
        />
        <ul>{values}</ul>
      </div>
    );
  }
);

Apply default value to widgets

A question that comes up frequently is “How to instantiate a refinement-list widget with a pre-selected item?”. For this use case, you can use the configure widget.

The following example instantiates a search page with a default query of “apple” and will show a category menu where the item “Cell Phones” is already selected:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { InstantSearch, SearchBox, RefinementList, Hits } from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <SearchBox defaultRefinement="apple" />
    <RefinementList attribute="categories" defaultRefinement="Cell Phones" />
    <Hits />
  </InstantSearch>
);

Virtual widgets

Many websites have “category pages” where the search context is already refined without the user having to do it. This constrains the search to only the specific results that you want to display. For example, an online shop for electronics devices could have a page like electronics.com/cell-phones that only shows cell phones:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import {
  connectRefinementList,
  Hits,
  InstantSearch,
  SearchBox,
} from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const VirtualRefinementList = connectRefinementList(() => null);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <VirtualRefinementList attribute="categories" defaultRefinement="Cell Phones" />
    <SearchBox />
    <Hits />
  </InstantSearch>
);

In this case, VirtualRefinementList is used with defaultRefinement to pre-refine results (within the categories search to only display Cell Phones). Think of the VirtualRefinementList as a hidden filter that defines attributes and values that you will always apply to search results.

Hiding default refinements

In some situations, not only do you want default refinements, but you also don’t want the user to be able to un-select them.

By default, the current-refinements widget or the useCurrentRefinements() connector will display the defaultRefinement. If you want to hide it, you need to filter the items with transformItems.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import {
  InstantSearch,
  CurrentRefinements,
  connectMenu,
} from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const VirtualMenu = connectMenu(() => null);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <VirtualMenu attribute="categories" defaultRefinement="Cell Phones" />
    <CurrentRefinements
      transformItems={items =>
        items.filter(item => item.currentRefinement !== 'Cell Phones')
      }
    />
  </InstantSearch>
);

Configure

It might happen that the predefined widgets don’t fit your use case. In that case, you can still apply search parameters by using the configure widget.

Here’s how you can then preselect a brand without even having to have the underlying RefinementList widget used:

1
2
3
4
5
<InstantSearch searchClient={searchClient} indexName="instant_search">
  <Configure filters="brand:Samsung" />
  <SearchBox />
  <Hits />
</InstantSearch>

Read the filtering guide to understand how filter syntax works and what can you put inside.

How to provide search parameters

Algolia has a wide range of parameters. If one of the parameters you want to use isn’t covered by any widget or connector, you can use the configure widget.

Here’s an example configuring the distinct parameter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { InstantSearch, Configure } from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'latency',
  '6be0576ff61c053d5f9a3225e2a90f76'
);

const App = () => (
  <InstantSearch indexName="instant_search" searchClient={searchClient}>
    <Configure distinct={1}/>
    {/* Widgets */}
  </InstantSearch>
);

Notes

Dynamic update of search parameters

Every applied search parameter can be retrieved by listening to the onSearchStateChange hook from the instantsearch root component.

But to update search parameters, you will need to pass updated props to the configure widget. Directly modifying the search state prop and injecting it will have no effect.

This example shows how to perform geo-searching with React InstantSearch and illustrates how to update search parameters.

Filtering results without widgets

Widgets already provide a lot of different ways to filter your results, but sometimes you might have more complicated use cases that require the usage of the filters search parameter.

Don’t use filters on a attribute already used with a widget, it will conflict.

1
<Configure filters="NOT categories:"Cell Phones"/>

Customize the complete UI of the widgets

Extending React InstantSearch widgets is the second layer of the API. Read about the two others possibilities in the “What’s InstantSearch?” guide.

This guide explains what you need to know before using connectors (the API feature behind extending widgets use case). Some parts may seem abstract at first, but they will make more sense once you try at least one connector.

When to extend widgets?

‘Extending widgets’ means redefining the rendering output of an existing widget. For instance, if you want to render the Menu widget as an HTML select element, you need to extend the Menu widget.

Here are some common examples that require the usage of the connectors API:

  • When you want to display widgets using another UI library like Material-UI
  • When you want to have complete control over the rendering without having to re-implement business logic
  • As soon as you hit a feature wall using the default widgets
  • When you are a React Native, read the guide on React Native for more information.

How widgets are built

React InstantSearch widgets are built in two parts:

  • Business logic code
  • Rendering code

The business logic is called connectors. They’re implemented with higher-order components. They encapsulate the logic for a specific kind of widget, and they provide a way to interact with the InstantSearch context. Those connectors allow you to completely extend existing widgets. The rendering is the React-specific code that tied to each platform (DOM or Native).

Connectors

Connectors are the API implementation Algolia provides for you to extend widgets. Connectors are functions you import and use to get the business logic of a particular widget.

There’s a 1-1 mapping between widgets and connectors: every widget has a connector and vice-versa. This means that you need to use its connector anytime you want to extend a widget.

Connectors for every widget are documented in the API reference, for example the menu widget.

Connectors render API

The aim is to share as much of a common API between all connectors as possible. Once you know how to use one connector, you can use them all.

As higher-order components, they have an outer component API (called exposed props). They will also provide some other props to the wrapped components - these are called the provided props.

Exposed props

Connectors expose props to configure their behavior. Like the attribute being refined in a menu. One common exposed prop that you can use is the defaultRefinement. Use it to provide the default refinement when the connected component is mounted.

Provided props

Most connectors use the same naming for properties passed down to your components.

  • items[]: array of items to display, for example, the brands list of a custom Refinement List. Every extended widget displaying a list gets an item’s property to the data passed to its render function.
  • refine(value|item.value): will refine the current state of the widget. Examples include: updating the query for a custom SearchBox or selecting a new item in a custom RefinementList.
  • currentRefinement: currently applied refinement value (usually the call value of refine()).
  • createURL(value|item.value): will return a full URL you can display for the specific refine value given you are using the routing feature.

An item is an object that you will find in items arrays. The shape of those objects is always the same.

  • item.value: The underlying precomputed state value to pass to refine or createURL
  • item.label: The label to display, for example, “Samsung”
  • item.count: The number of hits matching this item
  • item.isRefined: Is the item currently selected as a filter

Some connectors will have more data than others. Read their API reference to know more. Connectors for every widget are documented in the API reference, for example the menu widget.

Extending widget example

Below is a fully working example of a Menu widget rendered as a select HTML element.

The files to look at are src/MenuSelect.js, where you will find the code using connectMenu. And src/App.js shows how to use the newly created component.

Head over to the Discord server if you still have questions about extending widgets.

The search state

The state of all widgets is exposed in the searchState object. You can learn more about it in the dedicated section ui-state.

Widgets remain mandatory for applying state to queries. In other words, to apply a refinement, the widget that controls this refinement must be mounted on the page. For example, you can’t apply URL syncing without having a mounted <SearchBox> component. You can use a Virtual Widget to apply a refinement without having anything rendered on the page.

Did you find this page helpful?