diff --git a/packages/uikit-react-native/src/contexts/SendbirdChatCtx.tsx b/packages/uikit-react-native/src/contexts/SendbirdChatCtx.tsx index 29d39b732..3aec75ed5 100644 --- a/packages/uikit-react-native/src/contexts/SendbirdChatCtx.tsx +++ b/packages/uikit-react-native/src/contexts/SendbirdChatCtx.tsx @@ -1,5 +1,4 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { AppState, AppStateStatus } from 'react-native'; +import React, { useCallback, useState } from 'react'; import { useAppFeatures } from '@sendbird/uikit-chat-hooks'; import type { @@ -8,7 +7,7 @@ import type { SendbirdUser, SendbirdUserUpdateParams, } from '@sendbird/uikit-utils'; -import { confirmAndMarkAsDelivered, useForceUpdate } from '@sendbird/uikit-utils'; +import { confirmAndMarkAsDelivered, useAppState, useForceUpdate } from '@sendbird/uikit-utils'; import type EmojiManager from '../libs/EmojiManager'; import type ImageCompressionConfig from '../libs/ImageCompressionConfig'; @@ -123,16 +122,11 @@ export const SendbirdChatProvider = ({ [sdkInstance, appFeatures.deliveryReceiptEnabled], ); - useEffect(() => { - const listener = (status: AppStateStatus) => { - // 'active' | 'background' | 'inactive' | 'unknown' | 'extension'; - if (status === 'active') sdkInstance.connectionState === 'CLOSED' && sdkInstance.setForegroundState(); - else if (status === 'background') sdkInstance.connectionState === 'OPEN' && sdkInstance.setBackgroundState(); - }; - - const subscriber = AppState.addEventListener('change', listener); - return () => subscriber.remove(); - }, [sdkInstance]); + useAppState('change', (status) => { + // 'active' | 'background' | 'inactive' | 'unknown' | 'extension'; + if (status === 'active') sdkInstance.connectionState === 'CLOSED' && sdkInstance.setForegroundState(); + else if (status === 'background') sdkInstance.connectionState === 'OPEN' && sdkInstance.setBackgroundState(); + }); const value: Context = { sdk: sdkInstance, diff --git a/packages/uikit-react-native/src/fragments/createGroupChannelListFragment.tsx b/packages/uikit-react-native/src/fragments/createGroupChannelListFragment.tsx index d50b16fa3..c418cf014 100644 --- a/packages/uikit-react-native/src/fragments/createGroupChannelListFragment.tsx +++ b/packages/uikit-react-native/src/fragments/createGroupChannelListFragment.tsx @@ -1,8 +1,7 @@ -import React, { useEffect } from 'react'; -import { AppState } from 'react-native'; +import React from 'react'; import { useGroupChannelList } from '@sendbird/uikit-chat-hooks'; -import { PASS, useFreshCallback } from '@sendbird/uikit-utils'; +import { PASS, useAppState, useFreshCallback } from '@sendbird/uikit-utils'; import StatusComposition from '../components/StatusComposition'; import GroupChannelPreviewContainer from '../containers/GroupChannelPreviewContainer'; @@ -34,12 +33,9 @@ const createGroupChannelListFragment = (initModule?: Partial { - const listener = AppState.addEventListener('change', (status) => { - if (status === 'active') groupChannels.forEach(markAsDeliveredWithChannel); - }); - return () => listener.remove(); - }, []); + useAppState('change', (status) => { + if (status === 'active') groupChannels.forEach(markAsDeliveredWithChannel); + }); } const _renderGroupChannelPreview: GroupChannelListProps['List']['renderGroupChannelPreview'] = useFreshCallback( diff --git a/packages/uikit-utils/package.json b/packages/uikit-utils/package.json index 2a4c1ceb9..53d772c64 100644 --- a/packages/uikit-utils/package.json +++ b/packages/uikit-utils/package.json @@ -43,6 +43,7 @@ }, "devDependencies": { "@types/react": "*", + "@types/react-native": "*", "react": "17.0.2", "react-native": "0.67.4", "react-native-builder-bob": "^0.18.0", diff --git a/packages/uikit-utils/src/hooks/index.ts b/packages/uikit-utils/src/hooks/index.ts index 3d77085e2..abf159605 100644 --- a/packages/uikit-utils/src/hooks/index.ts +++ b/packages/uikit-utils/src/hooks/index.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { DependencyList } from 'react'; -import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; -import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'; type Destructor = () => void; type AsyncEffectCallback = () => void | Destructor | Promise | Promise; @@ -73,31 +72,3 @@ export const useFreshCallback = any>(callback: T) ref.current = callback; return useCallback(((...args) => ref.current(...args)) as T, []); }; - -type EdgePaddingMap = { - left: 'paddingLeft'; - right: 'paddingRight'; - top: 'paddingTop'; - bottom: 'paddingBottom'; -}; -const edgePaddingMap: EdgePaddingMap = { - left: 'paddingLeft', - right: 'paddingRight', - top: 'paddingTop', - bottom: 'paddingBottom', -}; -export const useSafeAreaPadding = < - T extends keyof EdgeInsets, - Result extends { [key in EdgePaddingMap[T]]: EdgeInsets[T] }, ->( - edges: T[], -): Result => { - const safeAreaInsets = useSafeAreaInsets(); - return useMemo(() => { - return edges.reduce((map, edge) => { - const paddingKey = edgePaddingMap[edge]; - map[paddingKey] = safeAreaInsets[edge]; - return map; - }, {} as { [key in EdgePaddingMap[T]]: EdgeInsets[T] }); - }, edges) as Result; -}; diff --git a/packages/uikit-utils/src/hooks/react-native.ts b/packages/uikit-utils/src/hooks/react-native.ts new file mode 100644 index 000000000..7b8225f86 --- /dev/null +++ b/packages/uikit-utils/src/hooks/react-native.ts @@ -0,0 +1,56 @@ +import { useEffect, useMemo, useRef } from 'react'; +import { AppState, AppStateEvent, AppStateStatus } from 'react-native'; +import { EdgeInsets, useSafeAreaInsets } from 'react-native-safe-area-context'; + +type EdgePaddingMap = { + left: 'paddingLeft'; + right: 'paddingRight'; + top: 'paddingTop'; + bottom: 'paddingBottom'; +}; +const edgePaddingMap: EdgePaddingMap = { + left: 'paddingLeft', + right: 'paddingRight', + top: 'paddingTop', + bottom: 'paddingBottom', +}; + +export const useSafeAreaPadding = < + T extends keyof EdgeInsets, + Result extends { [key in EdgePaddingMap[T]]: EdgeInsets[T] }, +>( + edges: T[], +): Result => { + const safeAreaInsets = useSafeAreaInsets(); + return useMemo(() => { + return edges.reduce((map, edge) => { + const paddingKey = edgePaddingMap[edge]; + map[paddingKey] = safeAreaInsets[edge]; + return map; + }, {} as { [key in EdgePaddingMap[T]]: EdgeInsets[T] }); + }, edges) as Result; +}; + +type AppStateListener = (status: AppStateStatus) => void; + +export const useAppState = (type: AppStateEvent, listener: AppStateListener) => { + const callbackRef = useRef(listener); + callbackRef.current = listener; + + useEffect(() => { + const eventListener = (state: AppStateStatus) => callbackRef.current(state); + const subscriber = AppState.addEventListener(type, eventListener); + + return () => { + // @ts-ignore + if (subscriber?.remove) { + subscriber.remove(); + } + // @ts-ignore + else if (AppState.removeEventListener) { + // @ts-ignore + AppState.removeEventListener(type, eventListener); + } + }; + }, []); +}; diff --git a/packages/uikit-utils/src/index.ts b/packages/uikit-utils/src/index.ts index 83c1640af..17014a35b 100644 --- a/packages/uikit-utils/src/index.ts +++ b/packages/uikit-utils/src/index.ts @@ -22,6 +22,7 @@ export { BufferedRequest } from './shared/bufferedRequest'; export * from './shared'; export * from './hooks'; +export * from './hooks/react-native'; export * from './ui-format/groupChannel'; export * from './ui-format/common'; export * from './sendbird/channel';