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

# Customize existing widgets

> Learn how you can customize an existing React InstantSearch widget.

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>;

<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 offers various APIs to let you **customize your search and discovery experience without considering its inner workings.**

For example, you can customize how the experience looks:

* [Highlight and snippet your search results](#highlight-and-snippet-your-search-results)
* [Style your widgets](#style-your-widgets)
* [Translate your widgets](#translate-your-widgets)
* [Using custom components with Hooks](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#custom-components-with-hooks)
* [Building a virtual widget with Hooks](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#build-virtual-widgets-with-hooks)
* [Using custom components without Hooks](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#custom-components-without-hooks)

You can also adjust the data sent to or received from Algolia:

* [Change the list of items in widgets](#change-the-list-of-items-in-widgets)
* [Apply default values to widgets](#apply-default-values-to-widgets)
* [Manually set search parameters](#manually-set-search-parameters)

## Highlight and snippet your search results

Search helps users understand what they're seeing in the results.
When users run a <SearchQuery />,
they should be able to tell why a result matches.
Algolia highlights the matched text in each result.
Algolia can also generate snippets:
short excerpts from longer text, truncated to a fixed length around the match.

<Info>
  Snippeted attributes are also highlighted.
  When working with [`Snippet`](/doc/api-reference/widgets/snippet/react),
  the attribute must be set up in [`attributesToSnippet`](/doc/api-reference/api-parameters/attributesToSnippet) either inside the Algolia dashboard or at runtime.
</Info>

### On the web

React InstantSearch provides the [`Highlight`](/doc/api-reference/widgets/highlight/react) and [`Snippet`](/doc/api-reference/widgets/snippet/react) components to highlight and snippet your Algolia search results. Both widgets take two props:

* `attribute`: the path to the highlighted attribute of the hit
* `hit`: a single result object

```jsx React icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import React from "react";
import { Highlight, Hits, InstantSearch, Snippet } from "react-instantsearch";

const appID = "ALGOLIA_APPLICATION_ID";
const apiKey = "ALGOLIA_SEARCH_API_KEY";

const searchClient = algoliasearch(appID, apiKey);

function Hit({ hit }) {
	return (
		<article>
			<h1>
				<Highlight attribute="name" hit={hit} />
			</h1>
			<Snippet hit={hit} attribute="description" />
		</article>
	);
}

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			<Hits hitComponent={Hit} />
		</InstantSearch>
	);
}
```

### With React Native

The [`Highlight`](/doc/api-reference/widgets/highlight/react) and [`Snippet`](/doc/api-reference/widgets/snippet/react) components are designed for the web platform.

To [highlight matches in a React Native app](/doc/guides/building-search-ui/going-further/native/react#highlight-matches),
you can build a custom `Highlight` component using the provided [`getHighlightedParts`](https://github.com/algolia/instantsearch/blob/master/packages/instantsearch.js/src/lib/utils/getHighlightedParts.ts) and [`getPropertyByPath`](https://github.com/algolia/instantsearch/blob/master/packages/instantsearch.js/src/lib/utils/getPropertyByPath.ts) utilities from `instantsearch.js`.

## Style your widgets

All React InstantSearch widgets use a set of conventional CSS classes compatible with the InstantSearch CSS themes.

If you don't want to use the themes, you can either write your own theme by following the existing classes and customizing the styles, or override the classes to pass yours instead.

### Load the InstantSearch theme

React InstantSearch doesn't inject any styles by default but comes with two optional themes: **Algolia** and **Satellite**.

#### From a CDN

The themes are available on jsDelivr.

Unminified:

* `https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/reset.css`
* `https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/algolia.css`
* `https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/satellite.css`

Minified:

* `https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/reset-min.css`
* `https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/algolia-min.css`
* `https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/satellite-min.css`

The `reset` style sheet is included by default in the Satellite theme,
so you don't need to import it separately.

Either copy the files into your own app or use a direct link to jsDelivr.

```html HTML icon=code-xml theme={"system"}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/satellite-min.css" integrity="sha256-W3Il6yFstioLHQJoDDU6iFn68e3hZpPPygHS2cjpHeo=" crossorigin="anonymous">
```

#### Using npm

If your project imports CSS into JavaScript files using build tools,
you can install the theme with npm.

<CodeGroup>
  ```sh npm theme={"system"}
  npm install instantsearch.css
  ```

  ```sh yarn theme={"system"}
  yarn add instantsearch.css
  ```

  ```sh pnpm theme={"system"}
  pnpm add instantsearch.css
  ```
</CodeGroup>

Then, you can import the theme directly into your app.

```js JavaScript icon=code theme={"system"}
import "instantsearch.css/themes/satellite.css";
```

### Write your own theme

You can create your own theme based on the InstantSearch CSS classes.

Either inspect the DOM with your developer tools and style accordingly or [reuse an existing theme and customize it](https://github.com/algolia/instantsearch/blob/master/packages/instantsearch.css/src/themes/algolia.scss).

```css CSS icon=paintbrush theme={"system"}
.ais-Breadcrumb-item--selected,
.ais-HierarchicalMenu-item--selected,
.ais-Menu-item--selected {
  font-weight: semibold;
}

/* ... */
```

### Customize with CSS variables

The InstantSearch.css themes provide CSS variables that you can override to customize colors, spacing, typography, and more without writing custom CSS classes.

```css CSS icon=paintbrush theme={"system"}
:root {
	/* Brand colors */
	--ais-primary-color-rgb: 62, 52, 211;

	/* Larger spacing */
	--ais-spacing-factor: 1.5;
}
```

For a complete list of available CSS variables and customization options, see [Styling and theming](/doc/guides/building-search-ui/styling/react).

### Pass custom CSS classes to widgets

If you're using a class-based CSS framework like [Bootstrap](https://getbootstrap.com/) or [Tailwind CSS](https://tailwindcss.com/),
you can pass your own CSS classes to the `classNames` prop to style each element of the widgets.

```jsx React icon=code theme={"system"}
// ...

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			<SearchBox
				classNames={{
					root: "p-3 shadow-sm",
					form: "relative",
					input:
						"block w-full pl-9 pr-3 py-2 bg-white border border-slate-300 placeholder-slate-400 focus:outline-none focus:border-sky-500 focus:ring-sky-500 rounded-md focus:ring-1",
					submitIcon: "absolute top-0 left-0 bottom-0 w-6",
				}}
			/>
			{/* ... */}
		</InstantSearch>
	);
}
```

If you only want to pass classes to the root element, use the [`className`](https://react.dev/learn#adding-styles) React prop.

```jsx React icon=code theme={"system"}
// ...

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			<SearchBox className="p-3 shadow-sm" />
			{/* ... */}
		</InstantSearch>
	);
}
```

## Translate your widgets

You can customize every piece of static text in React InstantSearch widgets,
such as "Load more" or "Show more" with the `translations` prop.
This is useful if you want to change the text or work with a localized app.

The `translations` prop is a key-value mapping.
Each translation is either a `string` or a `function` that exposes some state.
You can find the exact signature of the prop for each widget in their API reference.

### With strings

Most translations expect string values.

```jsx React icon=code theme={"system"}
// ...

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			<SearchBox
				translations={{
					submitButtonTitle: "Envoyer",
				}}
			/>
			{/* ... */}
		</InstantSearch>
	);
}
```

### With functions

For dynamic values, translations expose a function.

```jsx React icon=code theme={"system"}
// ...

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			{/* ... */}
			<RefinementList
				attribute="categories"
				translations={{
					showMoreButtonText: ({ isShowingMore }) =>
						isShowingMore ? "Voir moins" : "Voir plus",
				}}
			/>
		</InstantSearch>
	);
}
```

## Change the list of items in widgets

Every widget and Hook that handles a list of items exposes a `transformItems` prop that lets you transform the items before displaying them in the UI. This is useful to sort or filter items.

You can also use Hooks to add items that don't come from Algolia, or append cached results to the list of displayed items.

### Sort items

#### Using `transformItems`

This example uses the `transformItems` prop to order the current refinements by ascending `attribute`:

```jsx React icon=code theme={"system"}
// ...
import { orderBy } from "lodash";

// ...

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			{/* ... */}
			<CurrentRefinements
				transformItems={(items) => orderBy(items, "attribute", "asc")}
			/>
		</InstantSearch>
	);
}
```

#### Using `sortBy`

If you want to sort items in a [`RefinementList`](/doc/api-reference/widgets/refinement-list/react), use the [`sortBy`](/doc/api-reference/widgets/refinement-list/react#param-sort-by) prop.

```jsx React icon=code theme={"system"}
// ...

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			{/* ... */}
			<RefinementList attribute="categories" sortBy={["label:asc"]} />
		</InstantSearch>
	);
}
```

### Filter items

This example uses the `transformItems` prop to filter out items when the `count` is lower than 150.

```jsx React icon=code theme={"system"}
// ...

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

### Display static values

The facet values exposed in widgets like [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) or [`Menu`](/doc/api-reference/widgets/menu/react) are dynamic, and update with the context of the search. However, sometimes you may want to display a static list that never changes. You can do so using `transformItems`.

This example uses `transformItems` to display a static list of values.
This [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) always and only displays the items "iPad" and "Printers".

```jsx React icon=code theme={"system"}
// ...
import { InstantSearch, RefinementList } from "react-instantsearch-web";

// ...

const staticItems = [
	{ label: "Appliances", value: "Appliances" },
	{ label: "Cell Phones", value: "Cell Phones" },
];

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			{/* ... */}
			<RefinementList
				attribute="categories"
				transformItems={(items) => {
					return staticItems.map((staticItem) => ({
						...staticItem,
						...items.find((item) => item.value === staticItem.value),
					}));
				}}
			/>
		</InstantSearch>
	);
}
```

### Display facets with no matches

Showing facets that don't match the current query helps users explore and refine results.
Algolia returns only facet values that match the current query.
To show non-matching values, add frontend logic.

For example, you could cache facet values the first time you receive them.
Then, if Algolia returns fewer values than your facet limit,
append the cached values.

This approach has limitations:

* It doesn't work with [search for facet values](/doc/guides/managing-results/refine-results/faceting#search-for-facet-values) because Algolia doesn't return non-matching values. As a result, highlighting doesn't work for cached values.
* You may need to re-apply sorting in the [`transformItems`](#using-transformitems) function because InstantSearch applies its internal sorting before calling the function.

<CodeGroup>
  ```jsx App.js theme={"system"}
  import React from "react";
  import { InstantSearch } from "react-instantsearch";

  import { CustomRefinementList } from "./CustomRefinementList";
  import { indexName, searchClient } from "./searchClient";

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

  ```jsx CustomRefinementList.js theme={"system"}
  import React from "react";
  import { useRefinementList } from "react-instantsearch";

  import { indexName, searchClient } from "./searchClient";
  import { uniqBy } from "./uniqBy";

  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();

  export function CustomRefinementList(props) {
  	const { items, refine } = useRefinementList(props);

  	function toggle(item) {
  		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>
  	);
  }
  ```

  ```jsx searchClient.js theme={"system"}
  import { liteClient as algoliasearch } from "algoliasearch/lite";

  export const indexName = "instant_search";

  const appID = "ALGOLIA_APPLICATION_ID";
  const apiKey = "ALGOLIA_SEARCH_API_KEY";

  export const searchClient = algoliasearch(appID, apiKey);
  ```

  ```jsx uniqBy.js theme={"system"}
  export 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;
  		}
  	});
  }
  ```
</CodeGroup>

This solution comes with limitations:

* Facet hits from a [faceted search](/doc/guides/managing-results/refine-results/faceting#search-for-facet-values) won't work because Algolia only returns matching facets (the highlighting can't apply to cached items).
* You might need to sort again in the custom widget because the internal sorting happens before rendering.

## Apply default values to widgets

When first loading the page, you might want to assign default values to some widgets. For example, you may want to set a default filter based on a user setting, pre-select an item in a [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) when you're on a category page, or sync the UI with your URL state.

### Providing an initial state

Providing an initial state is useful when you want to start from a given [`uiState`](/doc/api-reference/widgets/ui-state/react) but you expect it to change from user interactions with the widgets.

This example provides an [`initialUiState`](/doc/api-reference/widgets/instantsearch/react#param-initial-ui-state) to React InstantSearch.

```jsx React icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import React from "react";
import { InstantSearch, Pagination, SearchBox } from "react-instantsearch";

const appID = "ALGOLIA_APPLICATION_ID";
const apiKey = "ALGOLIA_SEARCH_API_KEY";

const searchClient = algoliasearch(appID, apiKey);
const indexName = "instant_search";

function App() {
	return (
		<InstantSearch
			indexName={indexName}
			searchClient={searchClient}
			initialUiState={{
				[indexName]: {
					// Sets the initial search query for the SearchBox widget
					query: "phone",
					// Sets the initial page number for the Pagination widget
					page: 5,
				},
			}}
		>
			<SearchBox />
			{/* ... */}
			<Pagination />
		</InstantSearch>
	);
}
```

<Note>
  Since [`initialUiState`](/doc/api-reference/widgets/instantsearch/react#param-initial-ui-state)
  sets the initial state of your UI (widgets),
  you must also add the corresponding widgets to your implementation,
  or [use virtual widgets](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#build-virtual-widgets-with-hooks).
</Note>

### Sync UI state and URL

Syncing your UI with the browser URL is a good practice.
It lets users take one of your results pages,
copy the URL, and share it.
It also improves the user experience by enabling the use of the back and next browser buttons to keep track of previous searches.

To sync the UI state and the URL, use the [`routing`](/doc/api-reference/widgets/instantsearch/react#param-routing) option.

```jsx React icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import { InstantSearch, SearchBox } from "react-instantsearch";

const searchClient = algoliasearch(
	ALGOLIA_APPLICATION_ID,
	ALGOLIA_SEARCH_API_KEY,
);

function App() {
	return (
		<InstantSearch
			searchClient={searchClient}
			indexName="instant_search"
			routing={true}
		>
			<SearchBox />
			{/* ... */}
		</InstantSearch>
	);
}
```

<Note>
  Note the presence of the [`SearchBox`](/doc/api-reference/widgets/search-box/react) widget.
  When using [`routing`](/doc/api-reference/widgets/instantsearch/react#param-routing),
  you must also add the corresponding widgets to your implementation.
</Note>

To learn more, see [Sync your URLs](/doc/guides/building-search-ui/going-further/routing-urls/react).

## Manually set search parameters

Algolia supports a [wide range of search parameters](/doc/api-reference/search-api-parameters).
If you want to use a search parameter that isn't covered by any widget or Hook,
use the [`Configure`](/doc/api-reference/widgets/configure/react) widget.

Using [`Configure`](/doc/api-reference/widgets/configure/react) is also useful to provide a search parameter that users aren't meant to interact with directly.

```jsx React icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import React from "react";
import { Configure, InstantSearch } from "react-instantsearch";

const appID = "ALGOLIA_APPLICATION_ID";
const apiKey = "ALGOLIA_SEARCH_API_KEY";

const searchClient = algoliasearch(appID, apiKey);

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			<Configure analytics={false} distinct={true} getRankingInfo={true} />
		</InstantSearch>
	);
}
```

### Filter results without using widgets or Hooks

If you need to set a fixed filter without letting users change it,
pass [`filters`](/doc/api-reference/api-parameters/filters) to the [`Configure`](/doc/api-reference/widgets/configure/react) widget.
This can be useful, for example, to reflect user preferences saved in your app.

```jsx React icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import React from "react";
import { Configure, InstantSearch } from "react-instantsearch";

const appID = "ALGOLIA_APPLICATION_ID";
const apiKey = "ALGOLIA_SEARCH_API_KEY";

const searchClient = algoliasearch(appID, apiKey);

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			<Configure filters="free_shipping:true" />
		</InstantSearch>
	);
}
```

<Note>
  Don't set filters on [`Configure`](/doc/api-reference/widgets/configure/react) for an attribute already managed by a widget, or they will conflict.
</Note>

### Dynamically update search parameters

The [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) root component exposes an [`onStateChange`](/doc/api-reference/widgets/instantsearch/react#param-on-state-change) prop. This function triggers whenever the UI state changes (such as typing in a search box or selecting a refinement). You get a chance to alter the next UI state or perform custom logic before applying it.

In this example, there's a static "All" item at the top of the categories refinement list. When checked, it clears all existing category refinements. This is achieved by intercepting the next UI state in [`onStateChange`](/doc/api-reference/widgets/instantsearch/react#param-on-state-change) before it's applied and changing it with custom logic.

```jsx React icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import React from "react";
import { InstantSearch, RefinementList } from "react-instantsearch";

const appID = "ALGOLIA_APPLICATION_ID";
const apiKey = "ALGOLIA_SEARCH_API_KEY";

const searchClient = algoliasearch(appID, apiKey);

const indexName = "instant_search";

export function App() {
	return (
		<InstantSearch
			indexName={indexName}
			searchClient={searchClient}
			onStateChange={({ uiState, setUiState }) => {
				const categories = uiState[indexName].refinementList?.categories || [];
				const [lastSelectedCategory] = categories.slice(-1);

				setUiState({
					...uiState,
					[indexName]: {
						...uiState[indexName],
						refinementList: {
							...uiState[indexName].refinementList,
							categories: categories.filter((category) =>
								lastSelectedCategory === "All" ? false : category !== "All",
							),
						},
					},
				});
			}}
		>
			<RefinementList
				attribute="categories"
				transformItems={(items) =>
					[
						{
							label: "All",
							value: "All",
							count: null,
							isRefined: items.every((item) => !item.isRefined),
						},
					].concat(items)
				}
			/>
			{/* ... */}
		</InstantSearch>
	);
}
```

If you've set a search parameter using [`Configure`](/doc/api-reference/widgets/configure/react), you can update it like any React state.

```jsx React icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import React, { useEffect, useState } from "react";
import { Configure, InstantSearch } from "react-instantsearch";

const appID = "ALGOLIA_APPLICATION_ID";
const apiKey = "ALGOLIA_SEARCH_API_KEY";

const searchClient = algoliasearch(appID, apiKey);

const languages = [
	{ label: "Français", value: "fr_FR" },
	{ label: "English", value: "en_US" },
];

export function App() {
	const [language, setLanguage] = useState(languages[0].value);

	return (
		<>
			<label htmlFor="language">Language</label>
			<select
				id="language"
				value={language}
				onChange={(event) => setLanguage(event.target.value)}
			>
				{languages.map(({ label, value }) => (
					<option key={value} value={value}>
						{label}
					</option>
				))}
			</select>
			<InstantSearch indexName={indexName} searchClient={searchClient}>
				<Configure filters={`language:${language}`} />
				{/* ... */}
			</InstantSearch>
		</>
	);
}
```

## Customize the complete UI of the widgets

Hooks are the headless counterparts of widgets.
They return APIs to build the UI as you see fit.

If you feel limited by the provided customization options,
**you can use Hooks to control the render output.**
This is useful when [using React InstantSearch together with a component library](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#custom-components-with-hooks) or when [rendering to a non-DOM target like React Native](/doc/guides/building-search-ui/going-further/native/react).

Hooks can also be helpful if you want to create a custom behavior or a radically different UI based on the APIs of an existing widget.

For example, to set pre-determined search queries by clicking on a button,
you could use [`useSearchBox`](/doc/api-reference/widgets/search-box/react#hook) and render a button that sets a given query on click.

```jsx React icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import React from "react";
import { InstantSearch, useSearchBox } from "react-instantsearch";

const appID = "ALGOLIA_APPLICATION_ID";
const apiKey = "ALGOLIA_SEARCH_API_KEY";

const searchClient = algoliasearch(appID, apiKey);

function App() {
	return (
		<InstantSearch indexName="instant_search" searchClient={searchClient}>
			<PopularQueries queries={["apple", "samsung"]} />
		</InstantSearch>
	);
}

function PopularQueries({ queries, ...props }) {
	const { refine } = useSearchBox(props);

	return (
		<div>
			{queries.map((query) => (
				<button key={query} onClick={() => refine(query)}>
					Look for "{query}"
				</button>
			))}
		</div>
	);
}
```

Another example is when you need to create a [virtual widget](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#build-virtual-widgets-with-hooks) to apply a refinement without displaying anything on the page.

### When not to use Hooks

When you need to customize the UI of a widget, you might be tempted to use Hooks.
While this grants you complete control, it also means **you become responsible for adequately implementing UI logic and accessibility.**

The provided UI components already handle this complexity and provide many customization options for you to change the way they look. If you only need to make visual changes to a widget, **you should use customization options instead of reaching for Hooks.**

To summarize, avoid using Hooks to:

* Customize styles ([write your own theme](/doc/guides/building-search-ui/widgets/customize-an-existing-widget/react#write-your-own-theme), or [pass custom `classNames`](#pass-custom-css-classes-to-widgets) instead)
* Set DOM props on the top-level element (forward them on the widget directly instead)
* Transform the items before rendering them ([use `transformItems` when applicable](#change-the-list-of-items-in-widgets) instead)
* Apply default values ([use `initialUiState`](#apply-default-values-to-widgets) instead)

### Custom components with Hooks

If you're using a component library like [Material UI](https://mui.com/) or your own,
you might want to use these components instead of the provided ones.

You can use Hooks to access the necessary state and APIs to stitch React InstantSearch together with your custom components.

<CodeGroup>
  ```js App.js theme={"system"}
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import React from "react";
  import { InstantSearch } from "react-instantsearch";

  import { CustomSortBy } from "./CustomSortBy";

  const searchClient = algoliasearch("undefined", "undefined");

  function App() {
  	return (
  		<InstantSearch indexName="instant_search" searchClient={searchClient}>
  			<CustomSortBy
  				items={[
  					{ label: "Featured", value: "instant_search" },
  					{ label: "Price (asc)", value: "instant_search_price_asc" },
  					{ label: "Price (desc)", value: "instant_search_price_desc" },
  				]}
  			/>
  		</InstantSearch>
  	);
  }
  ```

  ```js CustomSortBy.js theme={"system"}
  import Box from "@mui/material/Box";
  import FormControl from "@mui/material/FormControl";
  import InputLabel from "@mui/material/InputLabel";
  import MenuItem from "@mui/material/MenuItem";
  import Select from "@mui/material/Select";
  import React from "react";
  import { useSortBy } from "react-instantsearch";

  export function CustomSortBy(props) {
  	const { currentRefinement, options, refine } = useSortBy(props);

  	return (
  		<Box>
  			<FormControl>
  				<InputLabel>Sort by</InputLabel>
  				<Select
  					value={currentRefinement}
  					onChange={(event) => refine(event.target.value)}
  				>
  					{options.map((item) => (
  						<MenuItem key={item.value} value={item.value}>
  							{item.label}
  						</MenuItem>
  					))}
  				</Select>
  			</FormControl>
  		</Box>
  	);
  }
  ```
</CodeGroup>

If you want to customize a widget, click its entry in the [React InstantSearch API reference](/doc/api-reference/widgets/react) and then look up the **Hook** section on that page.

### Build virtual widgets with Hooks

A virtual widget is an InstantSearch widget mounted in the app **but which doesn't render anything.**

Widgets do more than displaying a UI: they each interact with a piece of the UI state that maps to one or more Algolia search parameters. For example, the [`SearchBox`](/doc/api-reference/widgets/search-box/react) controls the [`query`](/doc/api-reference/api-parameters/query),
the [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) interacts with [`facetFilters`](/doc/api-reference/api-parameters/facetFilters), etc. **When mounting a widget or using the corresponding Hook, it's reflected in the InstantSearch UI state and included in search requests.**

For example, consider an ecommerce website where you want to have dedicated pages for your product categories.
The categories are provided by your server, and passed to InstantSearch using [`initialUiState`](#providing-an-initial-state).
The [`RefinementList`](/doc/api-reference/widgets/refinement-list/react) widget doesn't need to be displayed, but InstantSearch needs to be aware of its state.
You can solve this by using a virtual widget.

<CodeGroup>
  ```js VirtualRefinementList.js theme={"system"}
  import React from "react";
  import { useRefinementList } from "react-instantsearch";

  export function VirtualRefinementList(props) {
  	useRefinementList(props);

  	return null;
  }
  ```

  ```jsx App.jsx theme={"system"}
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import React from "react";
  import { InstantSearch } from "react-instantsearch";

  import { VirtualRefinementList } from "./VirtualRefinementList";

  const searchClient = algoliasearch("undefined", "undefined");

  // The category page value can be retrieved from the server.
  const categoryPage = "Audio";

  export function App() {
  	return (
  		<InstantSearch
  			searchClient={searchClient}
  			indexName="instant_search"
  			initialUiState={{
  				instant_search: {
  					refinementList: {
  						categories: [categoryPage],
  					},
  				},
  			}}
  		>
  			<VirtualRefinementList attribute="categories" />
  			{/* ... */}
  		</InstantSearch>
  	);
  }
  ```
</CodeGroup>

The virtual widget `VirtualRefinementList` refines results from Algolia according to the category retrieved from your server,
like a hidden filter that's always active.

### Custom components without Hooks

If you're using a component library like [Material UI](https://mui.com/) or your own,
you might want to use these components instead of the provided ones.

If your app uses class components instead of Hooks,
you can manually create a Higher Order Component (HOC) from the hook and then use the HOC to access the necessary state and APIs to stitch React InstantSearch together with your custom components.

<CodeGroup>
  ```js App.js theme={"system"}
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import React from "react";
  import { InstantSearch } from "react-instantsearch";

  import { CustomSortBy } from "./CustomSortBy";

  const searchClient = algoliasearch("undefined", "undefined");

  function App() {
  	return (
  		<InstantSearch indexName="instant_search" searchClient={searchClient}>
  			<CustomSortBy
  				items={[
  					{ label: "Featured", value: "instant_search" },
  					{ label: "Price (asc)", value: "instant_search_price_asc" },
  					{ label: "Price (desc)", value: "instant_search_price_desc" },
  				]}
  			/>
  		</InstantSearch>
  	);
  }
  ```

  ```js CustomSortBy.js theme={"system"}
  import Box from "@mui/material/Box";
  import FormControl from "@mui/material/FormControl";
  import InputLabel from "@mui/material/InputLabel";
  import MenuItem from "@mui/material/MenuItem";
  import Select from "@mui/material/Select";
  import React from "react";
  import { useSortBy } from "react-instantsearch";

  const connectSortBy = (Component) => (props) => {
  	const data = useSortBy(props);
  	return <Component {...data} {...props} />;
  };

  function RawCustomSortBy(props) {
  	const { currentRefinement, options, refine } = props;

  	return (
  		<Box>
  			<FormControl>
  				<InputLabel>Sort by</InputLabel>
  				<Select
  					value={currentRefinement}
  					onChange={(event) => refine(event.target.value)}
  				>
  					{options.map((item) => (
  						<MenuItem key={item.value} value={item.value}>
  							{item.label}
  						</MenuItem>
  					))}
  				</Select>
  			</FormControl>
  		</Box>
  	);
  }

  export const CustomSortBy = connectSortBy(RawCustomSortBy);
  ```
</CodeGroup>

If you want to customize a widget, click its entry in the [React InstantSearch API reference](/doc/api-reference/widgets/react) and then look up the **Hook** section on that page.

## Next steps

You now have a good starting point to create an even more custom experience with React InstantSearch.
Next up, you could improve this app by:

* [Create your own widget](/doc/guides/building-search-ui/widgets/create-your-own-widgets/react).
* Checking the [API reference](/doc/api-reference/widgets/instantsearch/react) to learn more about the widgets and Hooks.
