Skip to content

Commit 6240948

Browse files
Merge pull request #949 from GetStream/vishal/us-is-mounted-ref
fix: fixed react warnings - Can not perform a React state update on an unmounted component [CRNS-444]
2 parents 1224f5f + 7d2f362 commit 6240948

File tree

4 files changed

+64
-21
lines changed

4 files changed

+64
-21
lines changed

package/src/components/ChannelList/hooks/usePaginatedChannels.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
33
import { MAX_QUERY_CHANNELS_LIMIT } from '../utils';
44

55
import { useChatContext } from '../../../contexts/chatContext/ChatContext';
6+
import { useIsMountedRef } from '../../../hooks/useIsMountedRef';
67

78
import type { Channel, ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
89

@@ -58,9 +59,10 @@ export const usePaginatedChannels = <
5859
const [loadingChannels, setLoadingChannels] = useState(false);
5960
const [loadingNextPage, setLoadingNextPage] = useState(false);
6061
const [refreshing, setRefreshing] = useState(false);
62+
const isMounted = useIsMountedRef();
6163

6264
const queryChannels = async (queryType = '', retryCount = 0): Promise<void> => {
63-
if (!client || loadingChannels || loadingNextPage || refreshing) return;
65+
if (!client || loadingChannels || loadingNextPage || refreshing || !isMounted.current) return;
6466

6567
if (queryType === 'reload') {
6668
setLoadingChannels(true);
@@ -79,6 +81,8 @@ export const usePaginatedChannels = <
7981
try {
8082
const channelQueryResponse = await client.queryChannels(filters, sort, newOptions);
8183

84+
if (!isMounted.current) return;
85+
8286
channelQueryResponse.forEach((channel) => channel.state.setIsUpToDate(true));
8387

8488
const newChannels =
@@ -92,6 +96,8 @@ export const usePaginatedChannels = <
9296
} catch (err) {
9397
await wait(2000);
9498

99+
if (!isMounted.current) return;
100+
95101
if (retryCount === 3) {
96102
setLoadingChannels(false);
97103
setLoadingNextPage(false);

package/src/components/Chat/hooks/useIsOnline.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useCallback, useEffect, useState } from 'react';
22

33
import { useAppStateListener } from '../../../hooks/useAppStateListener';
4+
import { useIsMountedRef } from '../../../hooks/useIsMountedRef';
45
import { NetInfo } from '../../../native';
56

67
import type { NetInfoSubscription } from '@react-native-community/netinfo';
@@ -35,10 +36,9 @@ export const useIsOnline = <
3536
client: StreamChat<At, Ch, Co, Ev, Me, Re, Us>,
3637
closeConnectionOnBackground = true,
3738
) => {
38-
const [unsubscribeNetInfo, setUnsubscribeNetInfo] = useState<NetInfoSubscription>();
3939
const [isOnline, setIsOnline] = useState(true);
4040
const [connectionRecovering, setConnectionRecovering] = useState(false);
41-
41+
const isMounted = useIsMountedRef();
4242
const clientExits = !!client;
4343

4444
const onBackground = useCallback(() => {
@@ -83,35 +83,34 @@ export const useIsOnline = <
8383
}
8484
};
8585

86-
const setConnectionListener = () => {
86+
let unsubscribeNetInfo: NetInfoSubscription;
87+
const setNetInfoListener = () => {
8788
NetInfo.fetch().then((netInfoState) => {
8889
notifyChatClient(netInfoState);
8990
});
90-
setUnsubscribeNetInfo(
91-
NetInfo.addEventListener((netInfoState) => {
92-
notifyChatClient(netInfoState);
93-
}),
94-
);
91+
unsubscribeNetInfo = NetInfo.addEventListener((netInfoState) => {
92+
notifyChatClient(netInfoState);
93+
});
9594
};
9695

9796
const setInitialOnlineState = async () => {
9897
const status = await NetInfo.fetch();
99-
setIsOnline(status);
98+
99+
if (isMounted.current) setIsOnline(status);
100100
};
101101

102102
setInitialOnlineState();
103+
const chatListeners: Array<ReturnType<StreamChat['on']>> = [];
104+
103105
if (client) {
104-
client.on('connection.changed', handleChangedEvent);
105-
client.on('connection.recovered', handleRecoveredEvent);
106-
setConnectionListener();
106+
chatListeners.push(client.on('connection.changed', handleChangedEvent));
107+
chatListeners.push(client.on('connection.recovered', handleRecoveredEvent));
108+
setNetInfoListener();
107109
}
108110

109111
return () => {
110-
client.off('connection.changed', handleChangedEvent);
111-
client.off('connection.recovered', handleRecoveredEvent);
112-
if (unsubscribeNetInfo) {
113-
unsubscribeNetInfo();
114-
}
112+
chatListeners.forEach((listener) => listener.unsubscribe?.());
113+
unsubscribeNetInfo?.();
115114
};
116115
}, [clientExits]);
117116

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useEffect, useRef } from 'react';
2+
3+
/**
4+
* Returns mount status of component. This hook can be used for purpose of avoiding any
5+
* setState calls (within async operation) after component gets unmounted.
6+
*
7+
* @example
8+
* ```
9+
* const isMounted = useIsMountedRef();
10+
* const [dummyValue, setDummyValue] = useState(false);
11+
*
12+
* useEffect(() => {
13+
* someAsyncOperation().then(() => {
14+
* if (isMounted.current) setDummyValue(true);
15+
* })
16+
* })
17+
* ```
18+
*
19+
* @returns isMounted {Object} Mount status ref for the component.
20+
*/
21+
export const useIsMountedRef = () => {
22+
// Initial value has been set to true, since this hook exist only for sole purpose of
23+
// avoiding any setState calls (within async operation) after component gets unmounted.
24+
// Basically we don't need to know about state change from component being initialized -> mounted.
25+
// We only need a state change from initialized or mounted state -> unmounted state.
26+
const isMounted = useRef(true);
27+
28+
useEffect(
29+
() => () => {
30+
isMounted.current = false;
31+
},
32+
[],
33+
);
34+
35+
return isMounted;
36+
};

package/src/hooks/useStreami18n.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { useEffect, useState } from 'react';
22

3-
import type { TranslationContextValue } from '../contexts/translationContext/TranslationContext';
3+
import { useIsMountedRef } from './useIsMountedRef';
44
import { Streami18n } from '../utils/Streami18n';
55

6+
import type { TranslationContextValue } from '../contexts/translationContext/TranslationContext';
7+
68
export const useStreami18n = ({
79
i18nInstance,
810
setTranslators,
@@ -11,7 +13,7 @@ export const useStreami18n = ({
1113
i18nInstance?: Streami18n;
1214
}) => {
1315
const [loadingTranslators, setLoadingTranslators] = useState(true);
14-
16+
const isMounted = useIsMountedRef();
1517
const i18nInstanceExists = !!i18nInstance;
1618
useEffect(() => {
1719
let streami18n: Streami18n;
@@ -26,7 +28,7 @@ export const useStreami18n = ({
2628
setTranslators((prevTranslator) => ({ ...prevTranslator, t })),
2729
);
2830
streami18n.getTranslators().then((translator) => {
29-
if (translator) setTranslators(translator);
31+
if (translator && isMounted.current) setTranslators(translator);
3032
});
3133

3234
setLoadingTranslators(false);

0 commit comments

Comments
 (0)