Skip to content

Commit 2598091

Browse files
Merge pull request #956 from GetStream/vishal/deleted-channel-error
fix: handling the errors when channel which is open gets deleted [CRNS-468]
2 parents 7abbb69 + 92a61dd commit 2598091

File tree

2 files changed

+61
-17
lines changed

2 files changed

+61
-17
lines changed

package/src/components/Channel/Channel.tsx

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ const ChannelWithContext = <
544544
colors: { black },
545545
},
546546
} = useTheme();
547-
547+
const [deleted, setDeleted] = useState(false);
548548
const [editing, setEditing] = useState<boolean | MessageType<At, Ch, Co, Ev, Me, Re, Us>>(false);
549549
const [error, setError] = useState(false);
550550
const [hasMore, setHasMore] = useState(true);
@@ -670,7 +670,7 @@ const ChannelWithContext = <
670670
const markRead: ChannelContextValue<At, Ch, Co, Ev, Me, Re, Us>['markRead'] = useRef(
671671
throttle(
672672
() => {
673-
if (channel?.disconnected || !channel?.getConfig?.()?.read_events) {
673+
if (!channel || channel?.disconnected || !clientChannelConfig?.read_events) {
674674
return;
675675
}
676676

@@ -778,18 +778,33 @@ const ChannelWithContext = <
778778
};
779779

780780
useEffect(() => {
781-
/**
782-
* The more complex sync logic around internet connectivity (NetInfo) is part of Chat.tsx
783-
* listen to client.connection.recovered and all channel events
784-
*/
785-
client.on('connection.recovered', connectionRecoveredHandler);
786-
client.on('connection.changed', connectionChangedHandler);
787-
channel?.on(handleEvent);
781+
const channelSubscriptions: Array<ReturnType<ChannelType['on']>> = [];
782+
const clientSubscriptions: Array<ReturnType<StreamChat['on']>> = [];
783+
784+
const subscribe = () => {
785+
if (!channel) return;
786+
787+
/**
788+
* The more complex sync logic around internet connectivity (NetInfo) is part of Chat.tsx
789+
* listen to client.connection.recovered and all channel events
790+
*/
791+
clientSubscriptions.push(client.on('connection.recovered', connectionRecoveredHandler));
792+
clientSubscriptions.push(client.on('connection.changed', connectionChangedHandler));
793+
clientSubscriptions.push(
794+
client.on('channel.deleted', (event) => {
795+
if (event.cid === channel.cid) {
796+
setDeleted(true);
797+
}
798+
}),
799+
);
800+
channelSubscriptions.push(channel.on(handleEvent));
801+
};
802+
803+
subscribe();
788804

789805
return () => {
790-
client.off('connection.recovered', connectionRecoveredHandler);
791-
client.off('connection.changed', connectionChangedHandler);
792-
channel?.off(handleEvent);
806+
clientSubscriptions.forEach((s) => s.unsubscribe());
807+
channelSubscriptions.forEach((s) => s.unsubscribe());
793808
};
794809
}, [channelId, connectionRecoveredHandler, handleEvent]);
795810

@@ -1152,13 +1167,23 @@ const ChannelWithContext = <
11521167
}
11531168
};
11541169

1170+
// In case the channel is disconnected which may happen when channel is deleted,
1171+
// underlying js client throws an error. Following function ensures that Channel component
1172+
// won't result in error in such a case.
1173+
const getChannelConfigSafely = () => {
1174+
try {
1175+
return channel?.getConfig();
1176+
} catch (_) {
1177+
return null;
1178+
}
1179+
};
1180+
11551181
/**
11561182
* Channel configs for use in disabling local functionality.
11571183
* Nullish coalescing is used to give first priority to props to override
11581184
* the server settings. Then priority to server settings to override defaults.
11591185
*/
1160-
const clientChannelConfig =
1161-
typeof channel?.getConfig === 'function' ? channel.getConfig() : undefined;
1186+
const clientChannelConfig = getChannelConfigSafely();
11621187

11631188
const messagesConfig: MessagesConfig = {
11641189
/**
@@ -1597,7 +1622,7 @@ const ChannelWithContext = <
15971622
error,
15981623
giphyEnabled:
15991624
giphyEnabled ??
1600-
!!(channel?.getConfig?.()?.commands || [])?.some((command) => command.name === 'giphy'),
1625+
!!(clientChannelConfig?.commands || [])?.some((command) => command.name === 'giphy'),
16011626
hideDateSeparators,
16021627
hideStickyDateHeader,
16031628
isAdmin,
@@ -1780,6 +1805,9 @@ const ChannelWithContext = <
17801805
typing,
17811806
});
17821807

1808+
// TODO: replace the null view with appropriate message. Currently this is waiting a design decision.
1809+
if (deleted) return null;
1810+
17831811
if (!channel || (error && messages.length === 0)) {
17841812
return <LoadingErrorIndicator error={error} listType='message' retry={reloadChannel} />;
17851813
}

package/src/components/Channel/__tests__/Channel.test.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useContext, useEffect } from 'react';
22
import { View } from 'react-native';
3-
import { cleanup, render, waitFor } from '@testing-library/react-native';
3+
import { act, cleanup, render, waitFor } from '@testing-library/react-native';
44
import { StreamChat } from 'stream-chat';
55

66
import { Channel } from '../Channel';
@@ -14,8 +14,10 @@ import {
1414
MessagesProvider,
1515
} from '../../../contexts/messagesContext/MessagesContext';
1616
import { ThreadContext, ThreadProvider } from '../../../contexts/threadContext/ThreadContext';
17+
1718
import { useMockedApis } from '../../../mock-builders/api/useMockedApis';
1819
import { getOrCreateChannelApi } from '../../../mock-builders/api/getOrCreateChannel';
20+
import dispatchChannelDeletedEvent from '../../../mock-builders/event/channelDeleted';
1921
import { generateChannel } from '../../../mock-builders/generator/channel';
2022
import { generateMember } from '../../../mock-builders/generator/member';
2123
import { generateMessage } from '../../../mock-builders/generator/message';
@@ -65,6 +67,7 @@ describe('Channel', () => {
6567
chatClient = await getTestClientWithUser(user);
6668
useMockedApis(chatClient, [getOrCreateChannelApi(mockedChannel)]);
6769
channel = chatClient.channel('messaging', mockedChannel.id);
70+
channel.cid = mockedChannel.channel.cid;
6871
});
6972

7073
afterEach(() => {
@@ -77,7 +80,9 @@ describe('Channel', () => {
7780
...channel,
7881
cid: null,
7982
off: () => {},
80-
on: () => {},
83+
on: () => ({
84+
unsubscribe: () => null,
85+
}),
8186
watch: () => {},
8287
};
8388
const { getByTestId } = renderComponent({ channel: nullChannel });
@@ -193,6 +198,17 @@ describe('Channel', () => {
193198
await waitFor(() => expect(channelQuerySpy).toHaveBeenCalled());
194199
});
195200

201+
it('should render null if channel gets deleted', async () => {
202+
const { getByTestId, queryByTestId } = renderComponent({
203+
channel,
204+
children: <View testID='children' />,
205+
});
206+
207+
await waitFor(() => expect(getByTestId('children')).toBeTruthy());
208+
act(() => dispatchChannelDeletedEvent(chatClient, channel));
209+
expect(queryByTestId('children')).toBeNull();
210+
});
211+
196212
describe('ChannelContext', () => {
197213
it('renders children without crashing', async () => {
198214
const { getByTestId } = render(

0 commit comments

Comments
 (0)