import { StreamChat } from "stream-chat"; import { Chat, ChannelList, WithComponents } from "stream-chat-react"; import { Search } from "stream-chat-react/experimental"; const Component = () => ( <Chat client={client}> <WithComponents overrides={{ Search }}> <ChannelList // Enable search in ChannelList showChannelSearch={true} // Optional: Additional search props additionalChannelSearchProps={{ // Clear search on click outside clearSearchOnClickOutside: true, // Custom placeholder placeholder: "Search channels, messages and users...", // Custom debounce time for search debounceMs: 500, }} /> </WithComponents> </Chat> );Search
The React SDK has a default search functionality handled by ChannelSearch component. The ChannelSearch component, however, has some limitations:
- It is not customizable in terms of what is searched respectively what API endpoints are hit to retrieve the search results. The search logic is contained in a hook that is not customizable otherwise than with prop-drilled callbacks
- The search UI components cannot be customized otherwise than overriding the whole ChannelSearch component
The experimental Search component aims to address these limitations by the following means:
- Allows for limitless customization by relying on
SearchControllerclass that manages the search logic - The search UI components can be customized via
ComponentContextcreated with theWithComponentscomponent

Basic Usage
To replace the rendering of ChannelSearch by Search component, we need to import it from the experimental package and create a component context around the ChannelList that will render the Search:
Search Data Management
The search state is managed by SearchController class and reactive. It is exported by the stream-chat library. The class relies on so-called search sources to retrieve the data and take care of the pagination. There are three search sources available in stream-chat library:
ChannelSearchSource- queries channels bynamepartially matching the search queryUserSearchSource- queries users bynameoridpartially matching the search queryMessageSearchSource- queries messages and corresponding channels by messagetextpartially matching the search query
Customizing Search Data Retrieval
We can customize the retrieval parameters of the existing search sources as well as add new search sources that would retrieve custom data entities (other than channels, users or messages).
In the example below we demonstrate how to add a new custom search source. The pattern is however applicable to overriding the default search sources. Specifically, each new search source class has to implement abstract methods query and filterQueryResults. Also, the class should declare type attribute so that the SearchController can keep the source mapping.
import { useMemo } from "react"; import { BaseSearchSource, ChannelSearchSource, MessageSearchSource, SearchController, SearchSourceOptions, UserSearchSource, } from "stream-chat"; import { Chat, useCreateChatClient, WithComponents } from "stream-chat-react"; import { DefaultSearchResultItems, Search, SearchSourceResultList, } from "stream-chat-react/experimental"; // declare the type of item that is stored in the array by the search source type X = { x: string }; // declare the custom search source class XSearchSource extends BaseSearchSource<X> { // search source type is necessary readonly type = "X"; constructor(options?: SearchSourceOptions) { super(options); } // the query method will always receive the searched string protected async query(searchQuery: string) { return searchQuery.length > 1 ? { items: [{ x: "hi" }] } : { items: [{ x: "no" }] }; } // we can optionally manipulate the retrieved page of items protected filterQueryResults(items: X[]): X[] { return items; } } // we need a custom component to display the search source items const XSearchResultItem = ({ item }: { item: X }) => <div>{item.x}</div>; // and we tell the component that renders the resulting list, what components it can use to display the items const customSearchResultItems = { ...DefaultSearchResultItems, X: XSearchResultItem, }; const CustomSearchResultList = () => ( <SearchSourceResultList SearchResultItems={customSearchResultItems} /> ); const App = () => { const chatClient = useCreateChatClient<StreamChatGenerics>({ apiKey, tokenOrProvider: userToken, userData: { id: userId }, }); // create a memoized instance of SearchController const searchController = useMemo( () => chatClient ? new SearchController<StreamChatGenerics>({ sources: [ new XSearchSource(), new ChannelSearchSource<StreamChatGenerics>(chatClient), new UserSearchSource<StreamChatGenerics>(chatClient), new MessageSearchSource<StreamChatGenerics>(chatClient), ], }) : undefined, [chatClient], ); if (!chatClient) return <>Loading...</>; return ( <Chat client={chatClient} searchController={searchController}> <WithComponents overrides={{ Search, SearchSourceResultList: CustomSearchResultList, }} > {/* ....*/} </WithComponents> </Chat> ); };The search source query parameters like filters, sort or searchOptions are overridable by a simple assignment:
const { searchController } = useChatContext(); const usersSearchSource = searchController.getSource("users"); usersSearchSource.filters = { ...usersSearchSource.filters, myCustomField: "some-value", };Search UI Components And Their Customization
The default search UI components can be overridden through the component context, using the default component names. There are branch components that render other components and leaf components that render the markup.
Search
The top-level component for rendering SearchBar and SearchResults
SearchBar
A leaf component that handles the message input value
SearchResults
The top-level component for displaying search results for one or more search sources.
SearchResultsPresearch
The default component rendered by SearchResults when input value is an empty string - the pre-search state.
SearchResultsHeader
Rendered by SearchResults.The default component renders tags that determine what search source results will be displayed.
SearchSourceResults
Rendered by SearchResults. The component renders the UI components for specific search source listing:
SearchSourceResultsHeader- the default component does not render any markup. Can be used to add information about the source which items are being rendered in the listing below.SearchSourceResultsEmpty- rendered instead ofSearchSourceResultListSearchSourceResultList- renders items for a given search source
SearchSourceResultList
This is a child component of SearchSourceResults component. Renders a list of items in an InfiniteScrollPaginator component. Allows to specify React components for rendering search source items of a given type.
SearchSourceResultListFooter- component rendered at the bottom ofSearchSourceResultList. The default component informs user that more items are being loaded (SearchSourceResultsLoadingIndicator) or that there are no more items to be loaded.SearchSourceResultsLoadingIndicator- rendered bySearchSourceResultListFooter
Contexts
Search context
The main container component - Search - provides search context to child components.
directMessagingChannelType
The type of channel to create on user result select, defaults to messaging. This is just a forwarded value of Search component’s directMessagingChannelType prop.
| Type | Default |
|---|---|
| string | ’messaging’ |
disabled
Sets the input element into disabled state. This is just a forwarded value of Search component’s disabled prop.
| Type |
|---|
| boolean |
exitSearchOnInputBlur
Clear search state / search results on every click outside the search input. By default, the search UI is not removed on input blur. This is just a forwarded value of Search component’s exitSearchOnInputBlur prop.
| Type |
|---|
| boolean |
placeholder
Custom placeholder text to be displayed in the search input. This is just a forwarded value of Search component’s placeholder prop.
| Type |
|---|
| string |
searchController
Instance of the SearchController class that handles the data management. This is just a forwarded value of Chat component’s searchController prop. The child components can access the searchController state in a reactive manner.
import { SearchSourceResults, SearchResultsHeader, SearchResultsPresearch, useSearchContext, useStateStore, } from "stream-chat-react"; import type { SearchControllerState } from "stream-chat"; const searchControllerStateSelector = (nextValue: SearchControllerState) => ({ activeSources: nextValue.sources.filter((s) => s.isActive), isActive: nextValue.isActive, searchQuery: nextValue.searchQuery, }); export const SearchResults = () => { const { searchController } = useSearchContext<StreamChatGenerics>(); const { activeSources, isActive, searchQuery } = useStateStore( searchController.state, searchControllerStateSelector, ); return isActive ? ( <div> <SearchResultsHeader /> {!searchQuery ? ( <SearchResultsPresearch activeSources={activeSources} /> ) : ( activeSources.map((source) => ( <SearchSourceResults key={source.type} searchSource={source} /> )) )} </div> ) : null; };Search source context
The context is rendered by SearchSourceResults component. It provides the instance of search source class corresponding to the specified type. The child components can access the instance’s reactive state to render the data:
import { useSearchSourceResultsContext, useStateStore, } from "stream-chat-react"; import type { SearchSourceState } from "stream-chat"; const searchSourceStateSelector = (value: SearchSourceState) => ({ hasMore: value.hasMore, isLoading: value.isLoading, }); const Component = () => { const { searchSource } = useSearchSourceResultsContext(); const { hasMore, isLoading } = useStateStore( searchSource.state, searchSourceStateSelector, ); return ( <div> {isLoading ? ( <div>Is loading</div> ) : !hasMore ? ( <div>All results loaded</div> ) : null} </div> ); };