From 420f58fd31addfcfa021732b4a4a089cb314ef11 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 23 Nov 2023 17:15:58 +0100 Subject: [PATCH 1/2] Revert "feat: expose channels state on chat level (#2161)" This reverts commit 7e5543b3bf3961900c17e8a9283dfc64611f8660. --- .../components/contexts/chat-context.mdx | 98 ------------ .../ChannelList/__tests__/ChannelList.test.js | 148 +++++++++--------- .../ChannelList/hooks/usePaginatedChannels.ts | 7 +- src/components/Chat/Chat.tsx | 4 - .../Chat/hooks/useChannelsQueryState.ts | 2 +- src/components/Chat/hooks/useChat.ts | 3 - .../Chat/hooks/useCreateChatContext.ts | 6 - src/context/ChatContext.tsx | 29 +--- 8 files changed, 75 insertions(+), 222 deletions(-) diff --git a/docusaurus/docs/React/components/contexts/chat-context.mdx b/docusaurus/docs/React/components/contexts/chat-context.mdx index 906530e50..498edef25 100644 --- a/docusaurus/docs/React/components/contexts/chat-context.mdx +++ b/docusaurus/docs/React/components/contexts/chat-context.mdx @@ -33,34 +33,6 @@ The currently active channel, which populates the [`Channel`](../core-components | ------- | | Channel | -### channels - -State representing the array of loaded channels. Channels query is executed by default only within the [`ChannelList` component](../core-components/channel-list.mdx) in the SDK. - -| Type | -|-------------| -| `Channel[]` | - -### channelsQueryState - -Exposes API that: - -- indicates, whether and what channels query has been triggered within [`ChannelList` component](../core-components/channel-list.mdx) by its channels pagination controller - `queryInProgress` of type `ChannelQueryState` -- allows to set the `queryInProgress` state with `setQueryInProgress` state setter -- keeps track of error response from the channels query - `error` -- allows to set the `error` state with `setError` - -The `queryInProgress` values are: - -- `uninitialized` - the initial state before the first channels query is triggered -- `reload` - the initial channels query (loading the first page) is in progress -- `load-more` - loading the next page of channels -- `null` - at least one channels page has been loaded and there is no query in progress at the moment - -| Type | -|----------------------| -| `ChannelsQueryState` | - ### closeMobileNav The function to close mobile navigation. @@ -127,76 +99,6 @@ You can override the default behavior by pulling it from context and then utiliz | -------- | | function | -### setChannels - -Sets the list of `Channel` objects to be rendered by `ChannelList` component. One have to be careful, when to call `setChannels` as the first channels query executed by the `ChannelList` overrides the whole [`channels` state](#channels). In that case it is better to subscribe to `client` event `channels.queried` and only then set the channels. -In the following example, we have a component that sets the active channel based on the id in the URL. It waits until the first channels page is loaded, and then it sets the active channel. If the channel is not present on the first page, it performs additional API request with `getChannel()`: - -```tsx -import {useEffect} from 'react'; -import {useNavigate, useParams} from 'react-router-dom'; -import {ChannelList, getChannel, useChatContext} from 'stream-chat-react'; -import {ChannelFilters, ChannelOptions, ChannelSort, Event} from 'stream-chat'; - -const DEFAULT_CHANNEL = 'general'; -const CHANNEL_TYPE = 'messaging'; - -export const ChannelListWrapper = () => { - const { channelId } = useParams(); - const navigate = useNavigate(); - const { client, channel, setActiveChannel, setChannels } = useChatContext(); - - const filters: ChannelFilters = { type: CHANNEL_TYPE, members: { $in: [client.user?.id || ''] } }; - const options: ChannelOptions = { state: true, presence: true, limit: 10 }; - const sort: ChannelSort = { last_message_at: -1, updated_at: -1 }; - - // set active channel only if URL param changed - useEffect(() => { - if (!channelId) return navigate(`/${DEFAULT_CHANNEL}`); - - if (channel?.id === channelId || !client) return; - - let subscription: { unsubscribe: () => void } | undefined; - if(!channel?.id || channel?.id !== channelId) { - subscription = client.on('channels.queried', (event: Event) => { - // check, whether the channel has already been loaded with the first page - const loadedChannelData = event.queriedChannels?.channels.find((response) => response.channel.id === channelId); - - if (loadedChannelData) { - setActiveChannel(client.channel( CHANNEL_TYPE, channelId)); - subscription?.unsubscribe(); - return; - } - - return getChannel({client, id: channelId, type: CHANNEL_TYPE}).then((newActiveChannel) => { - setActiveChannel(newActiveChannel); - setChannels((channels) => { - return ([newActiveChannel, ...channels.filter((ch) => ch.data?.cid !== newActiveChannel.data?.cid)]); - }); - }); - }); - } - - return () => { - subscription?.unsubscribe(); - }; - }, [channel?.id, channelId, setChannels, client, navigate, setActiveChannel]); - - return ( - - ); -}; -``` - -| Type | -|---------------------------------------| -| `Dispatch>` | - ### theme Deprecated and to be removed in a future major release. Use the `customStyles` prop to adjust CSS variables and [customize the theme](../../guides/theming/css-and-theming.mdx#css-variables) of your app. diff --git a/src/components/ChannelList/__tests__/ChannelList.test.js b/src/components/ChannelList/__tests__/ChannelList.test.js index b9ce92b96..b18f4641d 100644 --- a/src/components/ChannelList/__tests__/ChannelList.test.js +++ b/src/components/ChannelList/__tests__/ChannelList.test.js @@ -48,21 +48,6 @@ const channelsQueryStateMock = { setQueryInProgress: jest.fn(), }; -const ChatContextOverrider = ({ chatContext, children }) => { - const existingContext = useChatContext(); - return ( - - {children} - - ); -}; - /** * We use the following custom UI components for preview and list. * If we use ChannelPreviewMessenger or ChannelPreviewLastMessage here, then changes @@ -131,7 +116,6 @@ describe('ChannelList', () => { client: chatClient, closeMobileNav, navOpen: true, - setChannels: jest.fn(), }} > @@ -163,7 +147,6 @@ describe('ChannelList', () => { client: chatClient, closeMobileNav, navOpen: false, - setChannels: jest.fn(), }} > @@ -409,7 +392,6 @@ describe('ChannelList', () => { describe('Default and custom active channel', () => { let setActiveChannel; - let setChannels; const watchersConfig = { limit: 20, offset: 0 }; const testSetActiveChannelCall = (channelInstance) => waitFor(() => { @@ -420,7 +402,6 @@ describe('ChannelList', () => { beforeEach(() => { setActiveChannel = jest.fn(); - setChannels = jest.fn(); useMockedApis(chatClient, [queryChannelsApi([testChannel1, testChannel2])]); }); @@ -431,7 +412,6 @@ describe('ChannelList', () => { channelsQueryState: channelsQueryStateMock, client: chatClient, setActiveChannel, - setChannels, }} > { channelsQueryState: channelsQueryStateMock, client: chatClient, setActiveChannel, - setChannels, }} > { }); it('should render channel with id `customActiveChannel` at top of the list', async () => { - useMockedApis(chatClient, [getOrCreateChannelApi(testChannel2)]); - jest - .spyOn(chatClient, 'queryChannels') - .mockImplementationOnce(() => - chatClient.hydrateActiveChannels([testChannel1, testChannel2]), - ); - await act(async () => { - await render( - - - , - ); - }); + const { container, getAllByRole, getByRole, getByTestId } = render( + + + , + ); // Wait for list of channels to load in DOM. - await waitFor(() => { - expect(screen.getByRole('list')).toBeInTheDocument(); - const items = screen.getAllByRole('listitem'); + await waitFor(async () => { + expect(getByRole('list')).toBeInTheDocument(); + const items = getAllByRole('listitem'); // Get the closest listitem to the channel that received new message. - const channelPreview = screen - .getByTestId(testChannel2.channel.id) - .closest(ROLE_LIST_ITEM_SELECTOR); + const channelPreview = getByTestId(testChannel2.channel.id).closest( + ROLE_LIST_ITEM_SELECTOR, + ); expect(channelPreview.isEqualNode(items[0])).toBe(true); + const results = await axe(container); + expect(results).toHaveNoViolations(); }); - jest.restoreAllMocks(); }); describe('channel search', () => { @@ -555,16 +535,20 @@ describe('ChannelList', () => { const renderComponents = (chatContext = {}, channeListProps) => render( - - - - - , + + + , ); it.each([['1'], ['2']])( @@ -1209,20 +1193,19 @@ describe('ChannelList', () => { it('should unset activeChannel if it was deleted', async () => { const setActiveChannel = jest.fn(); const { container, getByRole } = render( - - - - - , + + + , ); // Wait for list of channels to load in DOM. @@ -1274,21 +1257,32 @@ describe('ChannelList', () => { }); it('should unset activeChannel if it was hidden', async () => { + const setActiveChannel = jest.fn(); const { container, getByRole } = render( - - - , + + + , ); + // Wait for list of channels to load in DOM. await waitFor(() => { - expect(screen.getByTestId(testChannel1.channel.id)).toBeInTheDocument(); expect(getByRole('list')).toBeInTheDocument(); }); act(() => dispatchChannelHiddenEvent(chatClient, testChannel1.channel)); await waitFor(() => { - expect(screen.queryByTestId(testChannel1.channel.id)).not.toBeInTheDocument(); + expect(setActiveChannel).toHaveBeenCalledTimes(1); }); const results = await axe(container); expect(results).toHaveNoViolations(); diff --git a/src/components/ChannelList/hooks/usePaginatedChannels.ts b/src/components/ChannelList/hooks/usePaginatedChannels.ts index 182e847ec..ec955b11d 100644 --- a/src/components/ChannelList/hooks/usePaginatedChannels.ts +++ b/src/components/ChannelList/hooks/usePaginatedChannels.ts @@ -26,11 +26,9 @@ export const usePaginatedChannels = < recoveryThrottleIntervalMs: number = RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS, ) => { const { - channels, channelsQueryState: { error, setError, setQueryInProgress }, - setChannels, - } = useChatContext('usePaginatedChannels'); - + } = useChatContext('usePaginatedChannels'); + const [channels, setChannels] = useState>>([]); const [hasNextPage, setHasNextPage] = useState(true); const lastRecoveryTimestamp = useRef(); @@ -117,7 +115,6 @@ export const usePaginatedChannels = < queryChannels('reload'); }, [filterString, sortString]); - // FIXME: state refactor (breaking change) is needed - do not forward `channels` and `setChannel` return { channels, hasNextPage, diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx index 38835742f..ce91c8198 100644 --- a/src/components/Chat/Chat.tsx +++ b/src/components/Chat/Chat.tsx @@ -84,7 +84,6 @@ export const Chat = < const { channel, - channels, closeMobileNav, getAppSettings, latestMessageDatesByChannels, @@ -92,7 +91,6 @@ export const Chat = < navOpen, openMobileNav, setActiveChannel, - setChannels, translators, } = useChat({ client, defaultLanguage, i18nInstance, initialNavOpen }); @@ -109,7 +107,6 @@ export const Chat = < const chatContextValue = useCreateChatContext({ channel, - channels, channelsQueryState, client, closeMobileNav, @@ -120,7 +117,6 @@ export const Chat = < navOpen, openMobileNav, setActiveChannel, - setChannels, theme, themeVersion, useImageFlagEmojisOnWindows, diff --git a/src/components/Chat/hooks/useChannelsQueryState.ts b/src/components/Chat/hooks/useChannelsQueryState.ts index 37e9093d4..efec5e095 100644 --- a/src/components/Chat/hooks/useChannelsQueryState.ts +++ b/src/components/Chat/hooks/useChannelsQueryState.ts @@ -2,7 +2,7 @@ import { Dispatch, SetStateAction, useState } from 'react'; import type { APIErrorResponse, ErrorFromResponse } from 'stream-chat'; type ChannelQueryState = - | 'uninitialized' // the initial state before the first channels query is triggered + | 'uninitialized' // the initial state before the first channels query is trigerred | 'reload' // the initial channels query (loading the first page) is in progress | 'load-more' // loading the next page of channels | null; // at least one channels page has been loaded and there is no query in progress at the moment diff --git a/src/components/Chat/hooks/useChat.ts b/src/components/Chat/hooks/useChat.ts index 4ff91f7c4..2f949f2d6 100644 --- a/src/components/Chat/hooks/useChat.ts +++ b/src/components/Chat/hooks/useChat.ts @@ -36,7 +36,6 @@ export const useChat = < userLanguage: 'en', }); - const [channels, setChannels] = useState>>([]); const [channel, setChannel] = useState>(); const [mutes, setMutes] = useState>>([]); const [navOpen, setNavOpen] = useState(initialNavOpen); @@ -124,7 +123,6 @@ export const useChat = < return { channel, - channels, closeMobileNav, getAppSettings, latestMessageDatesByChannels, @@ -132,7 +130,6 @@ export const useChat = < navOpen, openMobileNav, setActiveChannel, - setChannels, translators, }; }; diff --git a/src/components/Chat/hooks/useCreateChatContext.ts b/src/components/Chat/hooks/useCreateChatContext.ts index b7b6366c3..3986ff253 100644 --- a/src/components/Chat/hooks/useCreateChatContext.ts +++ b/src/components/Chat/hooks/useCreateChatContext.ts @@ -10,7 +10,6 @@ export const useCreateChatContext = < ) => { const { channel, - channels, channelsQueryState, client, closeMobileNav, @@ -21,7 +20,6 @@ export const useCreateChatContext = < navOpen, openMobileNav, setActiveChannel, - setChannels, theme, themeVersion, useImageFlagEmojisOnWindows, @@ -39,7 +37,6 @@ export const useCreateChatContext = < const chatContext: ChatContextValue = useMemo( () => ({ channel, - channels, channelsQueryState, client, closeMobileNav, @@ -50,7 +47,6 @@ export const useCreateChatContext = < navOpen, openMobileNav, setActiveChannel, - setChannels, theme, themeVersion, useImageFlagEmojisOnWindows, @@ -59,12 +55,10 @@ export const useCreateChatContext = < channelCid, channelsQueryError, channelsQueryInProgress, - channels, clientValues, getAppSettings, mutedUsersLength, navOpen, - setChannels, ], ); diff --git a/src/context/ChatContext.tsx b/src/context/ChatContext.tsx index 14dfb75bd..f83140d79 100644 --- a/src/context/ChatContext.tsx +++ b/src/context/ChatContext.tsx @@ -1,4 +1,4 @@ -import React, { Dispatch, PropsWithChildren, SetStateAction, useContext } from 'react'; +import React, { PropsWithChildren, useContext } from 'react'; import type { AppSettingsAPIResponse, Channel, Mute } from 'stream-chat'; @@ -28,47 +28,20 @@ export type ThemeVersion = '1' | '2'; export type ChatContextValue< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics > = { - /** - * State representing the array of loaded channels. - * Channels query is executed by default only by ChannelList component in the SDK. - */ - channels: Channel[]; - /** - * Indicates, whether a channels query has been triggered within ChannelList by its channels pagination controller. - */ channelsQueryState: ChannelsQueryState; closeMobileNav: () => void; getAppSettings: () => Promise> | null; latestMessageDatesByChannels: Record; mutes: Array>; openMobileNav: () => void; - /** - * Sets active channel to be rendered within Channel component. - * @param newChannel - * @param watchers - * @param event - */ setActiveChannel: ( newChannel?: Channel, watchers?: { limit?: number; offset?: number }, event?: React.BaseSyntheticEvent, ) => void; - /** - * Sets the list of Channel objects to be rendered by ChannelList component. - */ - setChannels: Dispatch[]>>; - /** - * Allows to opt out of the use of legacy CSS (version "1") and opt into the use of the latest SDK's CSS (version "2"). - */ themeVersion: ThemeVersion; useImageFlagEmojisOnWindows: boolean; - /** - * Active channel used to render the contents of the Channel component. - */ channel?: Channel; - /** - * Object through which custom classes can be set for main container components of the SDK. - */ customClasses?: CustomClasses; navOpen?: boolean; } & Required, 'theme' | 'client'>>; From d71691e306c1017c789d74a6dbae28988eed2ab1 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 23 Nov 2023 17:30:20 +0100 Subject: [PATCH 2/2] docs: keep documentation for channelsQueryState & ChatContextValue --- .../components/contexts/chat-context.mdx | 20 +++++++++++++++++++ .../Chat/hooks/useChannelsQueryState.ts | 2 +- src/context/ChatContext.tsx | 18 +++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/React/components/contexts/chat-context.mdx b/docusaurus/docs/React/components/contexts/chat-context.mdx index 498edef25..f0d37d1ba 100644 --- a/docusaurus/docs/React/components/contexts/chat-context.mdx +++ b/docusaurus/docs/React/components/contexts/chat-context.mdx @@ -33,6 +33,26 @@ The currently active channel, which populates the [`Channel`](../core-components | ------- | | Channel | +### channelsQueryState + +Exposes API that: + +- indicates, whether and what channels query has been triggered within [`ChannelList` component](../core-components/channel-list.mdx) by its channels pagination controller - `queryInProgress` of type `ChannelQueryState` +- allows to set the `queryInProgress` state with `setQueryInProgress` state setter +- keeps track of error response from the channels query - `error` +- allows to set the `error` state with `setError` + +The `queryInProgress` values are: + +- `uninitialized` - the initial state before the first channels query is triggered +- `reload` - the initial channels query (loading the first page) is in progress +- `load-more` - loading the next page of channels +- `null` - at least one channels page has been loaded and there is no query in progress at the moment + +| Type | +|----------------------| +| `ChannelsQueryState` | + ### closeMobileNav The function to close mobile navigation. diff --git a/src/components/Chat/hooks/useChannelsQueryState.ts b/src/components/Chat/hooks/useChannelsQueryState.ts index efec5e095..37e9093d4 100644 --- a/src/components/Chat/hooks/useChannelsQueryState.ts +++ b/src/components/Chat/hooks/useChannelsQueryState.ts @@ -2,7 +2,7 @@ import { Dispatch, SetStateAction, useState } from 'react'; import type { APIErrorResponse, ErrorFromResponse } from 'stream-chat'; type ChannelQueryState = - | 'uninitialized' // the initial state before the first channels query is trigerred + | 'uninitialized' // the initial state before the first channels query is triggered | 'reload' // the initial channels query (loading the first page) is in progress | 'load-more' // loading the next page of channels | null; // at least one channels page has been loaded and there is no query in progress at the moment diff --git a/src/context/ChatContext.tsx b/src/context/ChatContext.tsx index f83140d79..cc2ed4e5e 100644 --- a/src/context/ChatContext.tsx +++ b/src/context/ChatContext.tsx @@ -28,20 +28,38 @@ export type ThemeVersion = '1' | '2'; export type ChatContextValue< StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics > = { + /** + * Indicates, whether a channels query has been triggered within ChannelList by its channels pagination controller. + */ channelsQueryState: ChannelsQueryState; closeMobileNav: () => void; getAppSettings: () => Promise> | null; latestMessageDatesByChannels: Record; mutes: Array>; openMobileNav: () => void; + /** + * Sets active channel to be rendered within Channel component. + * @param newChannel + * @param watchers + * @param event + */ setActiveChannel: ( newChannel?: Channel, watchers?: { limit?: number; offset?: number }, event?: React.BaseSyntheticEvent, ) => void; + /** + * Allows to opt out of the use of legacy CSS (version "1") and opt into the use of the latest SDK's CSS (version "2"). + */ themeVersion: ThemeVersion; useImageFlagEmojisOnWindows: boolean; + /** + * Active channel used to render the contents of the Channel component. + */ channel?: Channel; + /** + * Object through which custom classes can be set for main container components of the SDK. + */ customClasses?: CustomClasses; navOpen?: boolean; } & Required, 'theme' | 'client'>>;