Search customization
The search bar above the channel list finds channels, people and messages. This recipe changes the search bar's settings, replaces how channel results look, and moves search out of the sidebar into a top bar. How search works across the layers, and what each result does when clicked, is on Search.
Best practices
- Override the smallest part you need:
Searchto set its props,SearchSourceResultListto change one source's results,SearchBarorSearchResultsfor the rest. - Start from
DefaultSearchResultItemsand replace one entry, so the other sources keep their default results. - Keep what a result does when clicked. A channel result opens the channel, watches it, and adds it to the channel lists that match it.
- Keep
role="option"on a custom result's button. The arrow keys move between the results that have it.
Configure search through WithComponents
ChannelNavigation renders the channel list header, the search bar and the channel lists. It renders search from the Search key, so to change the search bar's props, set a component on that key that renders Search with them:
import { ChannelNavigation, Search, WithComponents } from "stream-chat-react";
const SidebarSearch = () => (
<Search
directMessagingChannelType="team"
exitSearchOnInputBlur
placeholder="Search teammates, channels and messages"
/>
);
const overrides = { Search: SidebarSearch };
export const Sidebar = () => (
<WithComponents overrides={overrides}>
<ChannelNavigation />
</WithComponents>
);directMessagingChannelTypeis the channel type a user result opens; the default ismessaging.exitSearchOnInputBlurcloses search when focus moves from the input to something outside search, such as a click elsewhere.placeholder,disabledandinputPropsgo to the input.
The key applies only where the SDK renders search. A Search you render yourself takes the props directly, as in Render search outside ChannelList.
Customize search result items
SearchSourceResultList renders the results of one source. Its SearchResultItems prop maps each source's type (channels, users, messages) to the component that renders one result. This list renders channel results as compact rows with the channel's name and member count, and keeps the default user and message results:
import type { PropsWithChildren } from "react";
import type { Channel, ChannelStateData } from "stream-chat";
import {
DefaultSearchResultItems,
SearchSourceResultList,
useChannelDisplayName,
useChatContext,
useStateStore,
useWorkspaceNavigation,
WithComponents,
} from "stream-chat-react";
const memberCountSelector = ({ memberCount }: ChannelStateData) => ({
memberCount,
});
const CompactChannelResult = ({ item }: { item: Channel }) => {
const { channelManager, client } = useChatContext();
const { openChannel } = useWorkspaceNavigation();
const name = useChannelDisplayName(item);
const { memberCount } = useStateStore(item.state, memberCountSelector);
return (
<button
className="channel-result"
onClick={(event) => {
openChannel(item, { event });
// Channel search doesn't watch its results, so watch the channel to receive its events.
item.watch().catch((error: Error) =>
client.notifications.addError({
message: "Failed to load the channel",
options: { originalError: error },
origin: { emitter: "CompactChannelResult" },
}),
);
// Add the channel to the channel lists whose filters it matches.
channelManager.ingestChannel(item);
}}
role="option"
type="button"
>
<span className="channel-result__name"># {name}</span>
<span className="channel-result__members">{memberCount} members</span>
</button>
);
};
const searchResultItems = {
...DefaultSearchResultItems,
channels: CompactChannelResult,
};
const ResultList = () => (
<SearchSourceResultList SearchResultItems={searchResultItems} />
);
const overrides = { SearchSourceResultList: ResultList };
export const WithCompactChannelResults = ({ children }: PropsWithChildren) => (
<WithComponents overrides={overrides}>{children}</WithComponents>
);The click does what the default channel result does:
openChannelhands the channel to the layout, which decides where it opens. The event is passed on, so ⌘/ctrl-click can open it beside the current one.Channel.watch()watches it. A failed watch publishes an error notification, as the default does.ChannelManager.ingestChannel()adds it to the registered channel lists that match it, so it shows in the list without a new query.
To keep the default result and only add to it, wrap it instead. ChannelSearchResultItem, UserSearchResultItem and MessageSearchResultItem are exported, and each takes onSelect to replace its click. The Search feature page wraps the default user result this way.
Render search outside ChannelList
Search reads the search controller from Chat, so it works anywhere inside Chat. This workspace puts it in a top bar and keeps the channel lists in the sidebar:
import {
ChannelHeader,
ChannelLists,
MessageComposer,
MessageList,
Search,
ThreadHeader,
} from "stream-chat-react";
import {
ChannelSlot,
ChatView,
ThreadSlot,
} from "stream-chat-react/slot-layout";
import type { LayoutDescriptor } from "stream-chat-react/slot-layout";
import "./Workspace.css";
const layouts: LayoutDescriptor[] = [
{ id: "channels", slots: ["channel", "thread"] },
];
const ChannelsView = () => (
<>
<header className="top-bar">
<Search placeholder="Search everything" />
</header>
<aside className="sidebar">
<ChannelLists />
</aside>
<ChannelSlot slot="channel">
<ChannelHeader />
<MessageList />
<MessageComposer />
</ChannelSlot>
<ThreadSlot slot="thread">
<ThreadHeader />
<MessageList />
<MessageComposer />
</ThreadSlot>
</>
);
export const Workspace = () => (
<ChatView layouts={layouts} views={{ channels: <ChannelsView /> }} />
);.top-bar {
position: relative;
}
/* The results open under the search bar, over the content below it. */
.top-bar .str-chat__search-results {
position: absolute;
inset-inline: 0;
top: 100%;
z-index: 10;
max-height: 60vh;
overflow-y: auto;
background: #ffffff;
}ChannelLists renders every list registered on client.channelManager. Unlike ChannelNavigation, it doesn't hide the lists while search is open, so the sidebar stays as it is and the results show in the top bar. The results render inside Search, under its search bar, so the CSS above lifts them over the content as a dropdown. Workspace with ChatView explains layouts, views and slots.
Go lower-level when you need custom data sources
The components above change how search looks. What it searches comes from the SearchController that Chat holds, with one source per kind of result:
- To search fewer kinds of results, or to change a source's filters, pass your own controller to
ChatassearchController. The Search feature page shows how, under "Choosing what to search". - To search your own data, such as projects or documents, write a source as Adding your own source describes, add it to the controller, and map its
typeinSearchResultItemsas above.