Skip to content

Commit

Permalink
feat: allow to search for channels only
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela committed Jan 30, 2025
1 parent 6bd855b commit 94482dc
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 41 deletions.
50 changes: 50 additions & 0 deletions src/components/ChannelSearch/__tests__/ChannelSearch.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,56 @@ describe('ChannelSearch', () => {
jest.useRealTimers();
});

it('search is performed on channels only', async () => {
const limit = 8;
const otherUsers = Array.from({ length: limit }, generateUser);
jest.useFakeTimers('modern');
const client = await getTestClientWithUser(user);
jest.spyOn(client, 'queryUsers').mockResolvedValue({ users: [...otherUsers, user] });
jest.spyOn(client, 'queryChannels').mockResolvedValue([channelResponseData]);

const { typeText } = await renderSearch({
client,
props: { searchForChannels: true, searchForUsers: false },
});
await act(() => {
typeText(typedText);
});

await act(() => {
jest.advanceTimersByTime(1000);
});

expect(client.queryUsers).not.toHaveBeenCalled();
expect(client.queryChannels).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});

it('search is not performed on channels neither users', async () => {
const limit = 8;
const otherUsers = Array.from({ length: limit }, generateUser);
jest.useFakeTimers('modern');
const client = await getTestClientWithUser(user);
jest.spyOn(client, 'queryUsers').mockResolvedValue({ users: [...otherUsers, user] });
jest.spyOn(client, 'queryChannels').mockResolvedValue([channelResponseData]);

const { typeText } = await renderSearch({
client,
props: { searchForChannels: false, searchForUsers: false },
});
await act(() => {
typeText(typedText);
});

await act(() => {
jest.advanceTimersByTime(1000);
});

expect(client.queryUsers).not.toHaveBeenCalled();
expect(client.queryChannels).not.toHaveBeenCalled();
jest.useRealTimers();
});

it('does not perform search queries when the search is disabled', async () => {
jest.useFakeTimers('modern');
const client = await getTestClientWithUser(user);
Expand Down
108 changes: 67 additions & 41 deletions src/components/ChannelSearch/hooks/useChannelSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ export type ChannelSearchParams<
) => Promise<void> | void;
/** The number of milliseconds to debounce the search query. The default interval is 200ms. */
searchDebounceIntervalMs?: number;
/** Boolean to search for channels as well as users in the server query, default is false and just searches for users */
/** Boolean to search for channels in the server query, default is false and just searches for users */
searchForChannels?: boolean;
/** Boolean to search for users in the server query, default is true and just searches for users */
searchForUsers?: boolean;
/** Custom search function to override the default implementation */
searchFunction?: (
params: ChannelSearchFunctionParams<StreamChatGenerics>,
Expand Down Expand Up @@ -99,6 +101,7 @@ export const useChannelSearch = <
onSelectResult,
searchDebounceIntervalMs = 300,
searchForChannels = false,
searchForUsers = true,
searchFunction,
searchQueryParams,
setChannels,
Expand All @@ -110,10 +113,7 @@ export const useChannelSearch = <
const [results, setResults] = useState<Array<ChannelOrUserResponse<StreamChatGenerics>>>([]);
const [searching, setSearching] = useState(false);

const searchQueryPromiseInProgress = useRef<
| Promise<UsersAPIResponse<StreamChatGenerics>>
| Promise<[Channel<StreamChatGenerics>[], UsersAPIResponse<StreamChatGenerics>]>
>(undefined);
const searchQueryPromiseInProgress = useRef<boolean>(false);
const shouldIgnoreQueryResults = useRef(false);

const inputRef = useRef<HTMLInputElement | null>(null);
Expand All @@ -124,9 +124,7 @@ export const useChannelSearch = <
setResults([]);
setSearching(false);

if (searchQueryPromiseInProgress.current) {
shouldIgnoreQueryResults.current = true;
}
shouldIgnoreQueryResults.current = searchQueryPromiseInProgress.current;
}, []);

const activateSearch = useCallback(() => {
Expand Down Expand Up @@ -210,41 +208,59 @@ export const useChannelSearch = <

const getChannels = useCallback(
async (text: string) => {
if (!searchForChannels && !searchForUsers) return;
let results: ChannelOrUserResponse<StreamChatGenerics>[] = [];
const promises: Array<
Promise<Channel<StreamChatGenerics>[]> | Promise<UsersAPIResponse<StreamChatGenerics>>
> = [];
try {
const userQueryPromise = client.queryUsers(
// @ts-expect-error
{
$or: [{ id: { $autocomplete: text } }, { name: { $autocomplete: text } }],
...searchQueryParams?.userFilters?.filters,
},
{ id: 1, ...searchQueryParams?.userFilters?.sort },
{ limit: 8, ...searchQueryParams?.userFilters?.options },
);

if (!searchForChannels) {
searchQueryPromiseInProgress.current = userQueryPromise;
const { users } = await searchQueryPromiseInProgress.current;
results = users.filter((u) => u.id !== client.user?.id);
} else {
const channelQueryPromise = client.queryChannels(
// @ts-expect-error
{
name: { $autocomplete: text },
...searchQueryParams?.channelFilters?.filters,
},
searchQueryParams?.channelFilters?.sort || {},
{ limit: 5, ...searchQueryParams?.channelFilters?.options },
if (searchForChannels) {
promises.push(
client.queryChannels(
// @ts-expect-error
{
members: { $in: [client.userID] },
name: { $autocomplete: text },
...searchQueryParams?.channelFilters?.filters,
},
searchQueryParams?.channelFilters?.sort || {},
{ limit: 5, ...searchQueryParams?.channelFilters?.options },
),
);
}

searchQueryPromiseInProgress.current = Promise.all([
channelQueryPromise,
userQueryPromise,
]);

const [channels, { users }] = await searchQueryPromiseInProgress.current;
if (searchForUsers) {
promises.push(
client.queryUsers(
// @ts-expect-error
{
$or: [{ id: { $autocomplete: text } }, { name: { $autocomplete: text } }],
...searchQueryParams?.userFilters?.filters,
},
{ id: 1, ...searchQueryParams?.userFilters?.sort },
{ limit: 8, ...searchQueryParams?.userFilters?.options },
),
);
}

results = [...channels, ...users.filter((u) => u.id !== client.user?.id)];
if (promises.length) {
searchQueryPromiseInProgress.current = true;

const resolved = await Promise.all(promises);

if (searchForChannels && searchForUsers) {
const [channels, { users }] = resolved as [
Channel<StreamChatGenerics>[],
UsersAPIResponse<StreamChatGenerics>,
];
results = [...channels, ...users.filter((u) => u.id !== client.user?.id)];
} else if (searchForChannels) {
const [channels] = resolved as [Channel<StreamChatGenerics>[]];
results = [...channels];
} else if (searchForUsers) {
const [{ users }] = resolved as [UsersAPIResponse<StreamChatGenerics>];
results = [...users.filter((u) => u.id !== client.user?.id)];
}
}
} catch (error) {
console.error(error);
Expand All @@ -257,9 +273,9 @@ export const useChannelSearch = <
shouldIgnoreQueryResults.current = false;
}

searchQueryPromiseInProgress.current = undefined;
searchQueryPromiseInProgress.current = false;
},
[client, searchForChannels, searchQueryParams],
[client, searchForChannels, searchForUsers, searchQueryParams],
);

// eslint-disable-next-line react-hooks/exhaustive-deps
Expand All @@ -282,6 +298,8 @@ export const useChannelSearch = <
},
event,
);
} else if (!searchForChannels && !searchForUsers) {
return;
} else if (event.target.value) {
setSearching(true);
setQuery(event.target.value);
Expand All @@ -292,7 +310,15 @@ export const useChannelSearch = <
}
onSearchCallback?.(event);
},
[clearState, disabled, scheduleGetChannels, onSearchCallback, searchFunction],
[
clearState,
disabled,
scheduleGetChannels,
onSearchCallback,
searchForChannels,
searchForUsers,
searchFunction,
],
);

return {
Expand Down

0 comments on commit 94482dc

Please sign in to comment.