diff --git a/docusaurus/docs/React/components/contexts/chat-context.mdx b/docusaurus/docs/React/components/contexts/chat-context.mdx index 906530e507..f0d37d1baf 100644 --- a/docusaurus/docs/React/components/contexts/chat-context.mdx +++ b/docusaurus/docs/React/components/contexts/chat-context.mdx @@ -33,14 +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: @@ -127,76 +119,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 b9ce92b96a..b18f4641dd 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 182e847ec1..ec955b11d0 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 38835742f0..ce91c8198c 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/useChat.ts b/src/components/Chat/hooks/useChat.ts index 4ff91f7c45..2f949f2d66 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 b7b6366c35..3986ff2539 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/components/MessageInput/index.ts b/src/components/MessageInput/index.ts index 2a7cd048ee..bdb11a9f16 100644 --- a/src/components/MessageInput/index.ts +++ b/src/components/MessageInput/index.ts @@ -5,6 +5,7 @@ export * from './EditMessageForm'; export * from './EmojiPicker'; export * from './hooks'; export * from './icons'; +export * from './LinkPreviewList'; export * from './MessageInput'; export * from './MessageInputFlat'; export * from './MessageInputSmall'; diff --git a/src/components/MessageList/index.ts b/src/components/MessageList/index.ts index 81712a1eb0..065d3818a2 100644 --- a/src/components/MessageList/index.ts +++ b/src/components/MessageList/index.ts @@ -1,6 +1,7 @@ export * from './ConnectionStatus'; // TODO: export this under its own folder export * from './GiphyPreviewMessage'; export * from './MessageList'; +export * from './MessageListNotifications'; export * from './MessageNotification'; export * from './ScrollToBottomButton'; export * from './VirtualizedMessageList'; diff --git a/src/context/ChatContext.tsx b/src/context/ChatContext.tsx index 14dfb75bd9..cc2ed4e5ed 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,11 +28,6 @@ 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. */ @@ -53,10 +48,6 @@ export type ChatContextValue< 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"). */ diff --git a/yarn.lock b/yarn.lock index 1b83c2be80..050e10783d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13436,9 +13436,9 @@ stream-browserify@^2.0.1: readable-stream "^2.0.2" stream-chat@^8.13.1: - version "8.14.0" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.14.0.tgz#ba96badaaf6c2d3025f31a6d5c0c5545d883c691" - integrity sha512-WEAssYcY/qSJXVK4B39JZJjyBzLSE4Wn+Gliywm8Nc2cmM0+fJF0853H5jZNy6AEeZhzxzRfxwq71r0FfZKudQ== + version "8.14.3" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-8.14.3.tgz#165402d2ed6fc4085f0cc0121b28c664159f8976" + integrity sha512-GYYf4bfpSdl4Itaw981D7R3OUiSBWUUOQypvUd6tvhs20O76Pu+gR/eOUkpl40jBfYSAFVkbhd/CnDFxJJafug== dependencies: "@babel/runtime" "^7.16.3" "@types/jsonwebtoken" "~9.0.0"