# Search

> **Info:** This is release candidate documentation for **Stream Chat React SDK v15**. For the latest stable version, see the [latest version (v14)](https://getstream.io/chat/docs/sdk/react/).

Search finds channels, people and messages from the search bar above the channel list. This page covers what users get, how the search UI connects to the state layer, and how to change what it searches and shows. How the sources query and paginate is on [Search sources](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/search-sources/).

## What users get

The user types "des" in the search bar. The channel list makes way for the results:

- Buttons for **channels**, **users** and **messages** choose what to search. One is selected at a time, channels first.
- The results load as they type, and more load as they scroll. Below the last one, "All results loaded" says there are no more.
- If they clear the text, the results area says "Start typing to search" until they type again.

What a result does when they click it:

| Result  | What happens                                                                                  |
| ------- | --------------------------------------------------------------------------------------------- |
| Channel | The channel opens, is watched, and appears in the channel lists it belongs to.                |
| Message | The message's channel opens and jumps to the message, which is highlighted.                   |
| User    | A direct message channel with that user opens. The server creates it if it doesn't exist yet. |

The clear button empties the search text. **Cancel**, or the Escape key, closes search, and the channel lists come back.

## How it works across the layers

### State: the search controller

Search is a `SearchController` from `stream-chat`, with one source per kind of result. The controller holds the search text and whether search is open. Each source holds its own results, page by page. The results are a snapshot: unlike a channel list, they don't gain or lose items as events arrive. [Search sources](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/search-sources/) explains the controller, the built-in sources and how each one queries.

`Chat` creates a controller with three sources, `ChannelSearchSource`, `UserSearchSource` and `MessageSearchSource`, and releases it when it unmounts. To use your own controller, pass it to `Chat` as `searchController`. Components read the controller with `useChatContext()`.

### UI: the search components

`Search` renders the search UI. It reads the controller from `Chat`, so it works anywhere inside `Chat`. `ChannelNavigation` renders it above the channel lists, and renders the lists only while search is closed.

`Search` is built from parts, each one a key you can override:

- `SearchBar`: the input, the clear button and the Cancel button. Typing opens search and passes the text to the controller.
- `SearchResults`: shown while search is open. It renders:
  - `SearchResultsHeader`, the buttons that choose the source;
  - `SearchResultsPresearch`, while the search text is empty;
  - `SearchSourceResults` for each active source: `SearchSourceResultsHeader`, which renders nothing by default, then `SearchSourceResultList` with the results, or `SearchSourceResultsEmpty` when the source found nothing.
- `SearchSourceResultList` renders the results in an infinite scroll, with `SearchSourceResultListFooter` and its `SearchSourceResultsLoadingIndicator` at the end.

`Search` provides `SearchContext`: the controller and the `Search` props. Inside `SearchSourceResults`, `useSearchSourceResultsContext()` gives the source that part renders.

### Opening a result

The result items open what was selected through `useWorkspaceNavigation()`, so the layout decides where it lands. Without a layout, nothing opens; see [Navigation](https://getstream.io/chat/docs/sdk/react/v15/ui/layout/navigation/).

Opening also changes state:

- A channel result watches the channel, because the channel search doesn't, and adds it to the lists that match it.
- A message result loads the message's channel if it isn't loaded, and jumps to the message with `channel.messagePaginator.jumpToMessage()`.
- A user result gets the direct message channel with `client.channel()`, watches it, and then adds it to the lists.

When the channel can't be loaded, the result publishes a "Failed to load the channel" notification. [Moving between channels, threads and messages](https://getstream.io/chat/docs/sdk/react/v15/ui/layout/moving-between-channels-threads-and-messages/#opening-a-channel-from-elsewhere) shows where each one opens.

## Turning it on or off

Search is on wherever `ChannelNavigation` or `Search` renders. To remove it from `ChannelNavigation`, set a component that renders nothing on the `Search` key:

```tsx
import type { PropsWithChildren } from "react";
import { WithComponents } from "stream-chat-react";

const NoSearch = () => null;

const overrides = { Search: NoSearch };

export const WithoutSearch = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

The search UI checks no capabilities. The server decides what each query returns, and message search covers only the channel types whose `search` setting is on; see [Channel types](https://getstream.io/chat/docs/javascript/channel-features/).

## Customizing

### Changing the search bar's behaviour

`Search` takes a few props: `placeholder`, `disabled`, `exitSearchOnInputBlur` to close search when focus leaves it, `inputProps` for the `<input>`, and `directMessagingChannelType`, the channel type a user result opens (`messaging` by default). To set them on the `Search` that `ChannelNavigation` renders, override the `Search` key with a component that renders `Search`:

```tsx
import type { PropsWithChildren } from "react";
import { Search, WithComponents } from "stream-chat-react";

const SidebarSearch = () => (
  <Search
    directMessagingChannelType="team"
    exitSearchOnInputBlur
    placeholder="Search channels, people and messages"
  />
);

const overrides = { Search: SidebarSearch };

export const WithSidebarSearch = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

### Choosing what to search

Pass your own controller to `Chat`. This one searches channels and users, but not messages:

```tsx
import { useEffect, useMemo } from "react";
import type { PropsWithChildren } from "react";
import {
  ChannelSearchSource,
  SearchController,
  UserSearchSource,
} from "stream-chat";
import type { StreamChat } from "stream-chat";
import { Chat } from "stream-chat-react";

export const ChatWithoutMessageSearch = ({
  children,
  client,
}: PropsWithChildren<{ client: StreamChat }>) => {
  const searchController = useMemo(
    () =>
      new SearchController({
        client,
        sources: [
          new ChannelSearchSource(client),
          new UserSearchSource(client),
        ],
      }),
    [client],
  );

  // Chat starts and releases only the controller it creates. This one is the app's.
  useEffect(() => {
    searchController.registerSubscriptions();
    return () => searchController.dispose();
  }, [searchController]);

  return (
    <Chat client={client} searchController={searchController}>
      {children}
    </Chat>
  );
};
```

Create the controller once per client, as `useMemo` does here. A new controller on every render starts every search from scratch. To change what a source queries, such as its filters and sort, configure the source; see [Search sources](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/search-sources/#built-in-sources).

### Changing how results look

`SearchSourceResultList` takes `SearchResultItems`, a map from a source's `type` to the component that renders one of its results. Each component gets the result as `item`. Start from `DefaultSearchResultItems` and replace one entry. This one keeps the default user result, with its click behaviour, and adds a label for admins:

```tsx
import type { PropsWithChildren } from "react";
import {
  DefaultSearchResultItems,
  SearchSourceResultList,
  UserSearchResultItem,
  WithComponents,
} from "stream-chat-react";
import type { UserSearchResultItemProps } from "stream-chat-react";

const UserResultWithRole = (props: UserSearchResultItemProps) => (
  <div className="user-result">
    <UserSearchResultItem {...props} />
    {props.item.role === "admin" && (
      <span className="user-result__role">Admin</span>
    )}
  </div>
);

const ResultList = () => (
  <SearchSourceResultList
    SearchResultItems={{
      ...DefaultSearchResultItems,
      users: UserResultWithRole,
    }}
  />
);

const overrides = { SearchSourceResultList: ResultList };

export const WithCustomResults = ({ children }: PropsWithChildren) => (
  <WithComponents overrides={overrides}>{children}</WithComponents>
);
```

`ChannelSearchResultItem`, `MessageSearchResultItem` and `UserSearchResultItem` also take `onSelect`, which runs instead of the default when the result is clicked.

### Reading search state in your own components

The controller and each source keep their state in stores. Inside `Search`, read them with `useSearchContext()` and `useSearchSourceResultsContext()`, and subscribe with `useStateStore`. This footer replaces `SearchSourceResultListFooter` and counts the loaded results:

```tsx
import type { SearchSourceState } from "stream-chat";
import {
  useSearchSourceResultsContext,
  useStateStore,
} from "stream-chat-react";

const footerSelector = ({ hasNext, isLoading, items }: SearchSourceState) => ({
  count: items?.length ?? 0,
  hasNext,
  isLoading,
});

export const ResultCountFooter = () => {
  const { searchSource } = useSearchSourceResultsContext();
  const { count, hasNext, isLoading } = useStateStore(
    searchSource.state,
    footerSelector,
  );

  if (isLoading) return <div className="result-footer">Loading…</div>;
  if (hasNext) return null;
  return <div className="result-footer">{count} results</div>;
};
```

Register it on the `SearchSourceResultListFooter` key. [Reading state in components](https://getstream.io/chat/docs/sdk/react/v15/ui/reading-state-in-components/) explains the selector rules.

### Searching your own data

A source of your own searches anything your app has, such as projects or documents. Write it as [Adding your own source](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/search-sources/#adding-your-own-source) describes, add it to the controller, and map its `type` to a result component:

```tsx
import { useEffect, useMemo } from "react";
import type { PropsWithChildren } from "react";
import {
  ChannelSearchSource,
  MessageSearchSource,
  SearchController,
  UserSearchSource,
} from "stream-chat";
import type { StreamChat } from "stream-chat";
import {
  Chat,
  DefaultSearchResultItems,
  SearchSourceResultList,
  WithComponents,
} from "stream-chat-react";
// A source with `type = "projects"`, built as the Search sources page shows.
import { ProjectSearchSource } from "./ProjectSearchSource";

type Project = { id: string; name: string };

const ProjectResult = ({ item }: { item: Project }) => (
  <button className="project-result" type="button">
    {item.name}
  </button>
);

const ResultList = () => (
  <SearchSourceResultList
    SearchResultItems={{ ...DefaultSearchResultItems, projects: ProjectResult }}
  />
);

const overrides = { SearchSourceResultList: ResultList };

export const ChatWithProjectSearch = ({
  children,
  client,
}: PropsWithChildren<{ client: StreamChat }>) => {
  const searchController = useMemo(
    () =>
      new SearchController({
        client,
        sources: [
          new ChannelSearchSource(client),
          new UserSearchSource(client),
          new MessageSearchSource(client),
          new ProjectSearchSource(),
        ],
      }),
    [client],
  );

  useEffect(() => {
    searchController.registerSubscriptions();
    return () => searchController.dispose();
  }, [searchController]);

  return (
    <Chat client={client} searchController={searchController}>
      <WithComponents overrides={overrides}>{children}</WithComponents>
    </Chat>
  );
};
```

The new source gets a button next to channels, users and messages. Its label is the translation of `search-results-header-filter-source-button-label--projects`, so add that key to your translations; [Localization](https://getstream.io/chat/docs/sdk/react/v15/ui/customizing/localization/) explains how.

The [Search customization](https://getstream.io/chat/docs/sdk/react/v15/recipes/channel-list/search-customization/) and [Search menu](https://getstream.io/chat/docs/sdk/react/v15/recipes/app/search-menu/) recipes go further.

## Reference

- [Search components](https://getstream.io/chat/docs/sdk/react/v15/component-reference/channel-list/search-components/): the `Search` props and the override keys.
- [ChatContext](https://getstream.io/chat/docs/sdk/react/v15/component-reference/chat/chatcontext/): `searchController`.
- [Chat](https://getstream.io/chat/docs/sdk/react/v15/component-reference/chat/chat/): the `searchController` prop.
- [Search sources](https://getstream.io/chat/docs/sdk/react/v15/state/loading-data/search-sources/): the controller, the sources and their state.

---

For the most recent version of this documentation, visit [https://getstream.io/chat/docs/sdk/react/v15/features/channels/search/](https://getstream.io/chat/docs/sdk/react/v15/features/channels/search/).