API Reference / React InstantSearch Widgets / Menu
Apr. 24, 2019
Widget signature
<Menu
  attribute={string}
  // Optional parameters
  defaultRefinement={string}
  limit={number}
  showMore={boolean}
  showMoreLimit={number}
  searchable={boolean}
  transformItems={function}
  translations={object}
/>

About this widget

The Menu widget displays a menu that lets the user choose a single value for a specific attribute.

Requirements

The attribute provided to the widget must be added in attributes for faceting, either on the dashboard or using attributesForFaceting with the API.

Examples

1
2
3
import { Menu } from 'react-instantsearch-dom';

<Menu attribute="brand" />

Props

attribute
type: string
Required

The name of the attribute in the record.

1
<Menu attribute='brand' />
defaultRefinement
type: string
Optional

The value of the item selected by default.

1
2
3
4
<Menu
  // ...
  defaultRefinement="Apple"
/>
limit
type: number
default: 10
Optional

The minimum number of diplayed items.

1
2
3
4
<Menu
  // ...
  limit={20}
/>
showMore
type: boolean
default: false
Optional

Whether to display a button that expands the number of items.

1
2
3
4
<Menu
  // ...
  showMore
/>
showMoreLimit
type: number
default: 20
Optional

The maximum number of displayed items. Only used when showMore is set to true.

1
2
3
4
<Menu
  // ...
  showMoreLimit={30}
/>
searchable
type: boolean
default: false
Optional

Whether to add a search input to let the user search for more facet values.

To make this feature work, you need to make the attribute searchable using the dashboard or using the searchable modifier of attributesForFaceting with the API.

1
2
3
4
<Menu
  // ...
  searchable
/>
transformItems
type: function
Optional

Modifies the items being displayed, for example, to filter or sort them. It takes items as argument and expects them back in return.

1
2
3
4
5
6
7
8
9
<Menu
  // ...
  transformItems={items =>
    items.map(item => ({
      ...item,
      label: item.label.toUpperCase(),
    }))
  }
/>
translations
type: object
Optional

A mapping of keys to translation values.

  • showMore: the label of the “Show more” button. Accepts one boolean parameter that is true if the values are expanded, false otherwise.
  • noResults: the label of the no results text when no results are found after a search for facet values.
1
2
3
4
5
6
7
8
9
<Menu
  // ...
  translations={{
    showMore(expanded) {
      return expanded ? 'Show less' : 'Show more';
    },
    noResults: 'No results',
  }}
/>

Customize the UI - connectMenu

If you want to create your own UI of the Menu widget or use another UI library, you can use connectors.

Connectors are higher-order components. They encapsulate the logic for a specific kind of widget and they provide a way to interact with the InstantSearch context.

They have an outer component API that we call exposed props, and they provide some other props to the wrapped components which are called the provided props.

This connector is also used to build other widgets: MenuSelect

It’s a 3-step process:

// 1. Create a React component
const Menu = () => {
  // return the DOM output
};

// 2. Connect the component using the connector
const CustomMenu = connectMenu(Menu);

// 3. Use your connected widget
<CustomMenu />

Create a React component

const Menu = ({
  object[] items,
  string currentRefinement,
  function refine,
  boolean isFromSearch,
  function searchForItems,
  function createURL,
}) => {
  // return the DOM output
};

Provided Props

items
type: object[]

The list of items the widget can display, with each item:

  • label: string: the label for the display of the refinement.
  • value: string: the value attached to this refinement.
  • count: number: the number of results that match the refinement.
  • isRefined: boolean: whether or not the value is selected.
1
2
3
4
5
6
7
8
9
10
11
const Menu = ({ items }) => (
  <ul>
    {items.map(item => (
      <li key={item.value}>
        <a href="#" style={{ fontWeight: item.isRefined ? 'bold' : '' }}>
          {item.label} ({item.count})
        </a>
      </li>
    ))}
  </ul>
);
currentRefinement
type: string

The currently applied refinement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const Menu = ({ items, currentRefinement, refine }) => (
  <div>
    <div>Current refinement: {currentRefinement}</div>
    <ul>
      {items.map(item => (
        <li key={item.value}>
          <a
            href="#"
            style={{ fontWeight: item.isRefined ? 'bold' : '' }}
            onClick={event => {
              event.preventDefault();
              refine(item.value);
            }}
          >
            {item.label} ({item.count})
          </a>
        </li>
      ))}
    </ul>
  </div>
);
refine
type: function

Toggles a refinement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Menu = ({ items, refine }) => (
  <ul>
    {items.map(item => (
      <li key={item.value}>
        <a
          href="#"
          style={{ fontWeight: item.isRefined ? 'bold' : '' }}
          onClick={event => {
            event.preventDefault();
            refine(item.value);
          }}
        >
          {item.label} ({item.count})
        </a>
      </li>
    ))}
  </ul>
);
isFromSearch
type: boolean

Whether the provided items contain facet values from the global search or from the search inside items.

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
const Menu = ({ items, isFromSearch, refine, searchForItems }) => (
  <ul>
    <li>
      <input
        type="search"
        onChange={event => searchForItems(event.currentTarget.value)}
      />
    </li>
    {items.map(item => (
      <li key={item.value}>
        <a
          href="#"
          style={{ fontWeight: item.isRefined ? 'bold' : '' }}
          onClick={event => {
            event.preventDefault();
            refine(item.value);
          }}
        >
          {isFromSearch ? (
            <Highlight attribute="label" hit={item} />
          ) : (
            item.label
          )}{' '}
          ({item.count})
        </a>
      </li>
    ))}
  </ul>
);
searchForItems
type: function

Triggers a search inside items values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const Menu = ({ items, refine, searchForItems }) => (
  <ul>
    <li>
      <input
        type="search"
        onChange={event => searchForItems(event.currentTarget.value)}
      />
    </li>
    {items.map(item => (
      <li key={item.value}>
        <a
          href="#"
          style={{ fontWeight: item.isRefined ? 'bold' : '' }}
          onClick={event => {
            event.preventDefault();
            refine(item.value);
          }}
        >
          {item.label} ({item.count})
        </a>
      </li>
    ))}
  </ul>
);
createURL
type: function

Generates a URL for the corresponding search state.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const Menu = ({ items, createURL }) => (
  <ul>
    {items.map(item => (
      <li key={item.value}>
        <a
          href={createURL(item.value)}
          style={{ fontWeight: item.isRefined ? 'bold' : '' }}
        >
          {item.label} ({item.count})
        </a>
      </li>
    ))}
  </ul>
);

Create and instantiate your connected widget

const CustomMenu = connectMenu(Menu);

<CustomMenu
  attribute={string}
  // optional parameters
  defaultRefinement={string}
  limit={number}
  showMore={boolean}
  showMoreLimit={number}
  searchable={boolean}
  transformItems={function}
/>

Exposed Props

attribute
type: string
Required

The name of the attribute in the record.

1
<CustomMenu attribute="brand" />
defaultRefinement
type: string
Optional

The value of the item selected by default.

1
2
3
4
<CustomMenu
  // ...
  defaultRefinement="Apple"
/>
limit
type: number
default: 10
Optional

The minimum number of diplayed items.

1
2
3
4
<CustomMenu
  // ...
  limit={20}
/>
showMore
type: boolean
default: false
Optional

We don’t expose the function to toggle the number of value displayed by the widget with the connector. Yet, the option is required to enable the “Show more” behavior. The option drives how many values are retrieved (either limit or showMoreLimit).

1
2
3
4
<CustomMenu
  // ...
  showMore
/>
showMoreLimit
type: number
default: 20
Optional

The maximum number of displayed items. Only used when showMore is set to true.

1
2
3
4
<CustomMenu
  // ...
  showMoreLimit={30}
/>
searchable
type: boolean
default: false
Optional

With the connector, it’s your responsability to render the SearchBox and trigger the search inside items. You have to provide the option, because the connector does an extra check to verify that you are in the right context to use the searchable feature.

To make this feature work, you need to make the attribute searchable using the dashboard or using the searchable modifier of attributesForFaceting with the API.

1
2
3
4
<CustomMenu
  // ...
  searchable
/>
transformItems
type: function
Optional

Modifies the items being displayed, for example, to filter or sort them. It takes items as argument and expects them back in return.

1
2
3
4
5
6
7
8
9
<CustomMenu
  // ...
  transformItems={items =>
    items.map(item => ({
      ...item,
      label: item.label.toUpperCase(),
    }))
  }
/>

If SEO is critical to your search page, your custom HTML markup needs to be parsable:

  • use plain <a> tags with href attributes for search engines bots to follow them,
  • use semantic markup with structured data when relevant, and test it.

Refer to our SEO checklist for building SEO-ready search experiences.

Full example

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
import { Highlight, connectMenu } from 'react-instantsearch-dom';

const Menu = ({ items, isFromSearch, refine, searchForItems, createURL }) => (
  <ul>
    <li>
      <input
        type="search"
        onChange={event => searchForItems(event.currentTarget.value)}
      />
    </li>
    {items.map(item => (
      <li key={item.value}>
        <a
          href={createURL(item.value)}
          style={{ fontWeight: item.isRefined ? 'bold' : '' }}
          onClick={event => {
            event.preventDefault();
            refine(item.value);
          }}
        >
          {isFromSearch ? (
            <Highlight attribute="label" hit={item} />
          ) : (
            item.label
          )}{' '}
          ({item.count})
        </a>
      </li>
    ))}
  </ul>
);

const CustomMenu = connectMenu(Menu);

HTML output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="ais-Menu">
  <div class="ais-Menu-searchBox">
    <!-- SearchBox widget here -->
  </div>
  <ul class="ais-Menu-list">
    <li class="ais-Menu-item ais-Menu-item--selected">
      <a class="ais-Menu-link" href="#">
        <span class="ais-Menu-label">Appliances</span>
        <span class="ais-Menu-count">4,306</span>
      </a>
    </li>
    <li class="ais-Menu-item">
      <a class="ais-Menu-link" href="#">
        <span class="ais-Menu-label">Audio</span>
        <span class="ais-Menu-count">1,570</span>
      </a>
    </li>
  </ul>
  <button class="ais-Menu-showMore">Show more</button>
</div>

Did you find this page helpful?