Skip to content

Commit

Permalink
fix: revert "feat: expose channels state on chat level (#2161)" (#2184)
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinCupela authored Dec 1, 2023
1 parent 2fcd564 commit 32e4867
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 183 deletions.
78 changes: 0 additions & 78 deletions docusaurus/docs/React/components/contexts/chat-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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 (
<ChannelList
setActiveChannelOnMount={false}
filters={filters}
sort={sort}
options={options}
/>
);
};
```

| Type |
|---------------------------------------|
| `Dispatch<SetStateAction<Channel[]>>` |

### 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.
Expand Down
148 changes: 71 additions & 77 deletions src/components/ChannelList/__tests__/ChannelList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,6 @@ const channelsQueryStateMock = {
setQueryInProgress: jest.fn(),
};

const ChatContextOverrider = ({ chatContext, children }) => {
const existingContext = useChatContext();
return (
<ChatContext.Provider
value={{
...existingContext,
channelsQueryState: channelsQueryStateMock,
...chatContext,
}}
>
{children}
</ChatContext.Provider>
);
};

/**
* We use the following custom UI components for preview and list.
* If we use ChannelPreviewMessenger or ChannelPreviewLastMessage here, then changes
Expand Down Expand Up @@ -131,7 +116,6 @@ describe('ChannelList', () => {
client: chatClient,
closeMobileNav,
navOpen: true,
setChannels: jest.fn(),
}}
>
<ChannelList {...props} />
Expand Down Expand Up @@ -163,7 +147,6 @@ describe('ChannelList', () => {
client: chatClient,
closeMobileNav,
navOpen: false,
setChannels: jest.fn(),
}}
>
<ChannelList {...props} />
Expand Down Expand Up @@ -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(() => {
Expand All @@ -420,7 +402,6 @@ describe('ChannelList', () => {

beforeEach(() => {
setActiveChannel = jest.fn();
setChannels = jest.fn();
useMockedApis(chatClient, [queryChannelsApi([testChannel1, testChannel2])]);
});

Expand All @@ -431,7 +412,6 @@ describe('ChannelList', () => {
channelsQueryState: channelsQueryStateMock,
client: chatClient,
setActiveChannel,
setChannels,
}}
>
<ChannelList
Expand Down Expand Up @@ -463,7 +443,6 @@ describe('ChannelList', () => {
channelsQueryState: channelsQueryStateMock,
client: chatClient,
setActiveChannel,
setChannels,
}}
>
<ChannelList
Expand All @@ -487,40 +466,41 @@ describe('ChannelList', () => {
});

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(
<Chat client={chatClient}>
<ChannelList
customActiveChannel={testChannel2.channel.id}
filters={{}}
List={ChannelListComponent}
options={{ presence: true, state: true, watch: true }}
Preview={ChannelPreviewComponent}
watchers={watchersConfig}
/>
</Chat>,
);
});
const { container, getAllByRole, getByRole, getByTestId } = render(
<ChatContext.Provider
value={{
channelsQueryState: channelsQueryStateMock,
client: chatClient,
setActiveChannel,
}}
>
<ChannelList
customActiveChannel={testChannel2.channel.id}
filters={{}}
List={ChannelListComponent}
options={{ presence: true, state: true, watch: true }}
Preview={ChannelPreviewComponent}
setActiveChannel={setActiveChannel}
setActiveChannelOnMount
watchers={watchersConfig}
/>
</ChatContext.Provider>,
);

// 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', () => {
Expand Down Expand Up @@ -555,16 +535,20 @@ describe('ChannelList', () => {

const renderComponents = (chatContext = {}, channeListProps) =>
render(
<Chat client={chatContext.client}>
<ChatContextOverrider chatContext={{ ...chatContext, setActiveChannel }}>
<ChannelList
filters={{}}
options={{ presence: true, state: true }}
showChannelSearch
{...channeListProps}
/>
</ChatContextOverrider>
</Chat>,
<ChatContext.Provider
value={{
channelsQueryState: channelsQueryStateMock,
setActiveChannel,
...chatContext,
}}
>
<ChannelList
filters={{}}
options={{ presence: true, state: true }}
showChannelSearch
{...channeListProps}
/>
</ChatContext.Provider>,
);

it.each([['1'], ['2']])(
Expand Down Expand Up @@ -1209,20 +1193,19 @@ describe('ChannelList', () => {
it('should unset activeChannel if it was deleted', async () => {
const setActiveChannel = jest.fn();
const { container, getByRole } = render(
<Chat client={chatClient}>
<ChatContextOverrider
chatContext={{
channelsQueryState: channelsQueryStateMock,
setActiveChannel,
}}
>
<ChannelList
{...channelListProps}
channel={{ cid: testChannel1.channel.cid }}
setActiveChannel={setActiveChannel}
/>
</ChatContextOverrider>
</Chat>,
<ChatContext.Provider
value={{
channelsQueryState: channelsQueryStateMock,
client: chatClient,
setActiveChannel,
}}
>
<ChannelList
{...channelListProps}
channel={{ cid: testChannel1.channel.cid }}
setActiveChannel={setActiveChannel}
/>
</ChatContext.Provider>,
);

// Wait for list of channels to load in DOM.
Expand Down Expand Up @@ -1274,21 +1257,32 @@ describe('ChannelList', () => {
});

it('should unset activeChannel if it was hidden', async () => {
const setActiveChannel = jest.fn();
const { container, getByRole } = render(
<Chat client={chatClient}>
<ChannelList {...channelListProps} />
</Chat>,
<ChatContext.Provider
value={{
channelsQueryState: channelsQueryStateMock,
client: chatClient,
setActiveChannel,
}}
>
<ChannelList
{...channelListProps}
channel={{ cid: testChannel1.channel.cid }}
setActiveChannel={setActiveChannel}
/>
</ChatContext.Provider>,
);

// 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();
Expand Down
7 changes: 2 additions & 5 deletions src/components/ChannelList/hooks/usePaginatedChannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,9 @@ export const usePaginatedChannels = <
recoveryThrottleIntervalMs: number = RECOVER_LOADED_CHANNELS_THROTTLE_INTERVAL_IN_MS,
) => {
const {
channels,
channelsQueryState: { error, setError, setQueryInProgress },
setChannels,
} = useChatContext<StreamChatGenerics>('usePaginatedChannels');

} = useChatContext('usePaginatedChannels');
const [channels, setChannels] = useState<Array<Channel<StreamChatGenerics>>>([]);
const [hasNextPage, setHasNextPage] = useState(true);
const lastRecoveryTimestamp = useRef<number | undefined>();

Expand Down Expand Up @@ -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,
Expand Down
4 changes: 0 additions & 4 deletions src/components/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,13 @@ export const Chat = <

const {
channel,
channels,
closeMobileNav,
getAppSettings,
latestMessageDatesByChannels,
mutes,
navOpen,
openMobileNav,
setActiveChannel,
setChannels,
translators,
} = useChat({ client, defaultLanguage, i18nInstance, initialNavOpen });

Expand All @@ -112,7 +110,6 @@ export const Chat = <

const chatContextValue = useCreateChatContext({
channel,
channels,
channelsQueryState,
client,
closeMobileNav,
Expand All @@ -123,7 +120,6 @@ export const Chat = <
navOpen,
openMobileNav,
setActiveChannel,
setChannels,
theme,
themeVersion,
useImageFlagEmojisOnWindows,
Expand Down
Loading

0 comments on commit 32e4867

Please sign in to comment.