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
SearchController
class that manages the search logic - The search UI components can be customized via
ComponentContext
created with theWithComponents
component
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 byname
partially matching the search queryUserSearchSource
- queries users byname
orid
partially matching the search queryMessageSearchSource
- queries messages and corresponding channels by messagetext
partially 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 ofSearchSourceResultList
SearchSourceResultList
- 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>
);
};