Skip to main content
The useMenu hook provides the logic to build custom menu (single-select facet) components.

Import

import { useMenu } from 'react-instantsearch';

Parameters

attribute
string
required
The attribute to create the menu for.
const { items, refine } = useMenu({ attribute: 'category' });
limit
number
default:"10"
Maximum number of items to display.
const { items } = useMenu({ 
  attribute: 'category', 
  limit: 5 
});
showMore
boolean
default:"false"
Whether to enable the “show more” functionality.
const { toggleShowMore } = useMenu({ 
  attribute: 'category', 
  showMore: true,
  showMoreLimit: 20
});
showMoreLimit
number
default:"20"
Maximum number of items to display when “show more” is active.
const hook = useMenu({ 
  attribute: 'category', 
  showMore: true,
  showMoreLimit: 50
});
sortBy
string[] | (a, b) => number
How to sort the menu items.
// Predefined sort orders
const hook = useMenu({ 
  attribute: 'category',
  sortBy: ['name:asc']
});

// Custom sort function
const hook2 = useMenu({ 
  attribute: 'category',
  sortBy: (a, b) => b.count - a.count
});
transformItems
(items: MenuItem[]) => MenuItem[]
Function to transform the items.
const { items } = useMenu({
  attribute: 'category',
  transformItems: (items) => 
    items.map((item) => ({
      ...item,
      label: item.label.toUpperCase()
    }))
});

Returns

items
MenuItem[]
The list of menu items.
const { items } = useMenu({ attribute: 'category' });

items.forEach((item) => {
  console.log(item.value);      // "Electronics"
  console.log(item.label);      // "Electronics"
  console.log(item.count);      // 142
  console.log(item.isRefined);  // true/false
});
refine
(value: string) => void
Function to select a menu item. Selecting an already selected item deselects it.
const { refine } = useMenu({ attribute: 'category' });
refine('Electronics'); // Select "Electronics"
refine('Electronics'); // Deselect "Electronics"
canRefine
boolean
Whether refinements can be applied.
const { canRefine } = useMenu({ attribute: 'category' });
if (!canRefine) {
  return <p>No categories available</p>;
}
isShowingMore
boolean
Whether “show more” is currently active.
const { isShowingMore } = useMenu({ 
  attribute: 'category',
  showMore: true 
});
toggleShowMore
() => void
Function to toggle between showing more/less items.
const { toggleShowMore } = useMenu({ 
  attribute: 'category',
  showMore: true 
});
canToggleShowMore
boolean
Whether the “show more” toggle is available.
const { canToggleShowMore } = useMenu({ 
  attribute: 'category',
  showMore: true 
});
createURL
(value: string) => string
Function to create a URL for a menu item.
const { createURL } = useMenu({ attribute: 'category' });
const url = createURL('Electronics');
sendEvent
SendEventForFacet
Function to send Insights events.
const { sendEvent } = useMenu({ attribute: 'category' });
sendEvent('click', 'Electronics', 'Category Selected');

Examples

Basic Menu

import { useMenu } from 'react-instantsearch';

function CategoryMenu() {
  const { items, refine } = useMenu({ attribute: 'category' });

  return (
    <div>
      <h3>Categories</h3>
      <ul>
        {items.map((item) => (
          <li key={item.value}>
            <button
              onClick={() => refine(item.value)}
              style={{ fontWeight: item.isRefined ? 'bold' : 'normal' }}
            >
              {item.label} ({item.count})
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Radio Button Style

import { useMenu } from 'react-instantsearch';

function RadioMenu() {
  const { items, refine } = useMenu({ attribute: 'category' });

  return (
    <div>
      <h3>Category</h3>
      <div>
        {items.map((item) => (
          <label key={item.value} style={{ display: 'block' }}>
            <input
              type="radio"
              name="category"
              checked={item.isRefined}
              onChange={() => refine(item.value)}
            />
            {item.label} ({item.count})
          </label>
        ))}
      </div>
    </div>
  );
}
import { useMenu } from 'react-instantsearch';

function DropdownMenu() {
  const { items, refine } = useMenu({ attribute: 'category' });
  const selectedItem = items.find((item) => item.isRefined);

  return (
    <div>
      <label htmlFor="category-select">Category:</label>
      <select
        id="category-select"
        value={selectedItem?.value || ''}
        onChange={(e) => refine(e.target.value)}
      >
        <option value="">All Categories</option>
        {items.map((item) => (
          <option key={item.value} value={item.value}>
            {item.label} ({item.count})
          </option>
        ))}
      </select>
    </div>
  );
}

With Show More

import { useMenu } from 'react-instantsearch';

function ExpandableMenu() {
  const { 
    items, 
    refine, 
    toggleShowMore, 
    isShowingMore, 
    canToggleShowMore 
  } = useMenu({ 
    attribute: 'category',
    limit: 5,
    showMore: true,
    showMoreLimit: 15
  });

  return (
    <div>
      <h3>Categories</h3>
      <ul>
        {items.map((item) => (
          <li key={item.value}>
            <button
              onClick={() => refine(item.value)}
              style={{ fontWeight: item.isRefined ? 'bold' : 'normal' }}
            >
              {item.label} ({item.count})
            </button>
          </li>
        ))}
      </ul>
      {canToggleShowMore && (
        <button onClick={toggleShowMore}>
          {isShowingMore ? 'Show less' : 'Show more'}
        </button>
      )}
    </div>
  );
}

Card Style Menu

import { useMenu } from 'react-instantsearch';

function CardMenu() {
  const { items, refine } = useMenu({ attribute: 'category' });

  return (
    <div>
      <h3>Browse by Category</h3>
      <div className="category-cards">
        {items.map((item) => (
          <div
            key={item.value}
            className={`category-card ${item.isRefined ? 'selected' : ''}`}
            onClick={() => refine(item.value)}
          >
            <h4>{item.label}</h4>
            <p>{item.count} items</p>
          </div>
        ))}
      </div>
    </div>
  );
}

With Icons

import { useMenu } from 'react-instantsearch';

const categoryIcons = {
  Electronics: '💻',
  Clothing: '👕',
  Books: '📚',
  Home: '🏠',
};

function IconMenu() {
  const { items, refine } = useMenu({ attribute: 'category' });

  return (
    <div>
      <h3>Categories</h3>
      <div className="icon-menu">
        {items.map((item) => (
          <button
            key={item.value}
            onClick={() => refine(item.value)}
            className={item.isRefined ? 'active' : ''}
          >
            <span className="icon">{categoryIcons[item.value]}</span>
            <span className="label">{item.label}</span>
            <span className="count">{item.count}</span>
          </button>
        ))}
      </div>
    </div>
  );
}

With Empty State

import { useMenu } from 'react-instantsearch';

function MenuWithEmptyState() {
  const { items, refine, canRefine } = useMenu({ attribute: 'category' });

  if (!canRefine) {
    return (
      <div>
        <h3>Categories</h3>
        <p>No categories available</p>
      </div>
    );
  }

  return (
    <div>
      <h3>Categories</h3>
      <ul>
        {items.map((item) => (
          <li key={item.value}>
            <button
              onClick={() => refine(item.value)}
              style={{ fontWeight: item.isRefined ? 'bold' : 'normal' }}
            >
              {item.label} ({item.count})
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Sorted Alphabetically

import { useMenu } from 'react-instantsearch';

function AlphabeticalMenu() {
  const { items, refine } = useMenu({ 
    attribute: 'category',
    sortBy: ['name:asc']
  });

  return (
    <div>
      <h3>Categories (A-Z)</h3>
      <ul>
        {items.map((item) => (
          <li key={item.value}>
            <button
              onClick={() => refine(item.value)}
              style={{ fontWeight: item.isRefined ? 'bold' : 'normal' }}
            >
              {item.label} ({item.count})
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}
import { useMenu } from 'react-instantsearch';
import Link from 'next/link';

function LinkMenu() {
  const { items, refine, createURL } = useMenu({ attribute: 'category' });

  return (
    <div>
      <h3>Categories</h3>
      <ul>
        {items.map((item) => (
          <li key={item.value}>
            <Link 
              href={createURL(item.value)}
              onClick={(e) => {
                e.preventDefault();
                refine(item.value);
              }}
              style={{ fontWeight: item.isRefined ? 'bold' : 'normal' }}
            >
              {item.label} ({item.count})
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

TypeScript

import { useMenu } from 'react-instantsearch';
import type { UseMenuProps } from 'react-instantsearch';

function CategoryMenu(props: UseMenuProps) {
  const { items, refine } = useMenu(props);

  return (
    <ul>
      {items.map((item) => (
        <li key={item.value}>
          <button onClick={() => refine(item.value)}>
            {item.label}
          </button>
        </li>
      ))}
    </ul>
  );
}