Skip to content

Commit c271c12

Browse files
authored
[SBISSUE-18637] Create store with the props as a default value (#1316)
### Ticket [SBISSUE-18637](https://sendbird.atlassian.net/browse/18637) ### Changelog * Create store instance with the props as default value. * Before stabilization refactoring, each provider set their context value synchronously if possible. But during refactoring, we put the all set up process in async effect hook, including initialization. This change caused various unexpected bugs. [SBISSUE-18637]: https://sendbird.atlassian.net/browse/SBISSUE-18637?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent bcc6b7f commit c271c12

File tree

11 files changed

+267
-53
lines changed

11 files changed

+267
-53
lines changed

src/lib/Sendbird/context/SendbirdContext.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createStore } from '../../../utils/storeManager';
33
import { initialState } from './initialState';
44
import { SendbirdState } from '../types';
55
import { useStore } from '../../../hooks/useStore';
6+
import { TwoDepthPartial } from '../../../utils/typeHelpers/partialDeep';
67

78
/**
89
* SendbirdContext
@@ -12,7 +13,25 @@ export const SendbirdContext = React.createContext<ReturnType<typeof createStore
1213
/**
1314
* Create store for Sendbird context
1415
*/
15-
export const createSendbirdContextStore = () => createStore(initialState);
16+
export const createSendbirdContextStore = (props?: TwoDepthPartial<SendbirdState>) => createStore({
17+
config: {
18+
...initialState.config,
19+
...props?.config,
20+
},
21+
stores: {
22+
...initialState.stores,
23+
...props?.stores,
24+
},
25+
eventHandlers: {
26+
...initialState.eventHandlers,
27+
...props?.eventHandlers,
28+
},
29+
emojiManager: initialState.emojiManager,
30+
utils: {
31+
...initialState.utils,
32+
...props?.utils,
33+
},
34+
});
1635

1736
/**
1837
* A specialized hook for Ssendbird state management

src/lib/Sendbird/context/SendbirdProvider.tsx

Lines changed: 67 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import React, { ReactElement, useEffect, useMemo, useRef, useState } from 'react
33
import { useUIKitConfig } from '@sendbird/uikit-tools';
44

55
/* Types */
6-
import type { ImageCompressionOptions, SendbirdProviderProps, SendbirdStateConfig } from '../types';
6+
import {
7+
ImageCompressionOptions,
8+
Logger,
9+
SendbirdProviderProps,
10+
SendbirdState,
11+
SendbirdStateConfig,
12+
} from '../types';
713

814
/* Providers */
915
import VoiceMessageProvider from '../../VoiceMessageProvider';
@@ -33,10 +39,10 @@ import { DEFAULT_MULTIPLE_FILES_MESSAGE_LIMIT, DEFAULT_UPLOAD_SIZE_LIMIT, VOICE_
3339
import { EmojiReactionListRoot, MenuRoot } from '../../../ui/ContextMenu';
3440

3541
import useSendbird from './hooks/useSendbird';
36-
import { SendbirdContext, useSendbirdStore } from './SendbirdContext';
37-
import { createStore } from '../../../utils/storeManager';
38-
import { initialState } from './initialState';
42+
import { createSendbirdContextStore, SendbirdContext, useSendbirdStore } from './SendbirdContext';
3943
import useDeepCompareEffect from '../../../hooks/useDeepCompareEffect';
44+
import { deleteNullish } from '../../../utils/utils';
45+
import { TwoDepthPartial } from '../../../utils/typeHelpers/partialDeep';
4046

4147
/**
4248
* SendbirdContext - Manager
@@ -49,6 +55,7 @@ const SendbirdContextManager = ({
4955
customWebSocketHost,
5056
configureSession,
5157
theme = 'light',
58+
logger,
5259
config = {},
5360
nickname = '',
5461
colorSet,
@@ -68,11 +75,10 @@ const SendbirdContextManager = ({
6875
eventHandlers,
6976
htmlTextDirection = 'ltr',
7077
forceLeftToRightMessageLayout = false,
71-
}: SendbirdProviderProps): ReactElement => {
78+
}: SendbirdProviderProps & { logger: Logger }): ReactElement => {
7279
const onStartDirectMessage = _onStartDirectMessage ?? _onUserProfileMessage;
73-
const { logLevel = '', userMention = {}, isREMUnitEnabled = false, pubSub: customPubSub } = config;
80+
const { userMention = {}, isREMUnitEnabled = false, pubSub: customPubSub } = config;
7481
const { isMobile } = useMediaQueryContext();
75-
const [logger, setLogger] = useState(LoggerFactory(logLevel as LogLevel));
7682
const [pubSub] = useState(customPubSub ?? pubSubFactory<PUBSUB_TOPICS, SBUGlobalPubSubTopicPayloadUnion>());
7783

7884
const { state, updateState } = useSendbirdStore();
@@ -121,11 +127,6 @@ const SendbirdContextManager = ({
121127
actions.disconnect({ logger });
122128
});
123129

124-
// to create a pubsub to communicate between parent and child
125-
useEffect(() => {
126-
setLogger(LoggerFactory(logLevel as LogLevel));
127-
}, [logLevel]);
128-
129130
// should move to reducer
130131
const [currentTheme, setCurrentTheme] = useState(theme);
131132
useEffect(() => {
@@ -365,8 +366,49 @@ const SendbirdContextManager = ({
365366
return null;
366367
};
367368

368-
const InternalSendbirdProvider = ({ children, stringSet, breakpoint, dateLocale }) => {
369-
const storeRef = useRef(createStore(initialState));
369+
const InternalSendbirdProvider = (props: SendbirdProviderProps & { logger: Logger }) => {
370+
const {
371+
children,
372+
stringSet,
373+
breakpoint,
374+
dateLocale,
375+
} = props;
376+
377+
const defaultProps: TwoDepthPartial<SendbirdState> = deleteNullish({
378+
config: {
379+
renderUserProfile: props?.renderUserProfile,
380+
onStartDirectMessage: props?.onStartDirectMessage,
381+
allowProfileEdit: props?.allowProfileEdit,
382+
appId: props?.appId,
383+
userId: props?.userId,
384+
accessToken: props?.accessToken,
385+
theme: props?.theme,
386+
htmlTextDirection: props?.htmlTextDirection,
387+
forceLeftToRightMessageLayout: props?.forceLeftToRightMessageLayout,
388+
pubSub: props?.config?.pubSub,
389+
logger: props?.logger,
390+
userListQuery: props?.userListQuery,
391+
voiceRecord: {
392+
maxRecordingTime: props?.voiceRecord?.maxRecordingTime ?? VOICE_RECORDER_DEFAULT_MAX,
393+
minRecordingTime: props?.voiceRecord?.minRecordingTime ?? VOICE_RECORDER_DEFAULT_MIN,
394+
},
395+
userMention: {
396+
maxMentionCount: props?.config?.userMention?.maxMentionCount || 10,
397+
maxSuggestionCount: props?.config?.userMention?.maxSuggestionCount || 15,
398+
},
399+
imageCompression: {
400+
compressionRate: 0.7,
401+
outputFormat: 'preserve',
402+
...props?.imageCompression,
403+
},
404+
disableMarkAsDelivered: props?.disableMarkAsDelivered,
405+
isMultipleFilesMessageEnabled: props?.isMultipleFilesMessageEnabled,
406+
},
407+
eventHandlers: props?.eventHandlers,
408+
});
409+
410+
const storeRef = useRef(createSendbirdContextStore(defaultProps));
411+
370412
const localeStringSet = useMemo(() => {
371413
return { ...getStringSet('en'), ...stringSet };
372414
}, [stringSet]);
@@ -391,11 +433,19 @@ const InternalSendbirdProvider = ({ children, stringSet, breakpoint, dateLocale
391433
};
392434

393435
export const SendbirdContextProvider = (props: SendbirdProviderProps) => {
394-
const { children } = props;
436+
const { children, config } = props;
437+
const logLevel = config?.logLevel;
438+
439+
const [logger, setLogger] = useState(LoggerFactory(logLevel as LogLevel));
440+
441+
// to create a pubsub to communicate between parent and child
442+
useEffect(() => {
443+
setLogger(LoggerFactory(logLevel as LogLevel));
444+
}, [logLevel]);
395445

396446
return (
397-
<InternalSendbirdProvider stringSet={props.stringSet} breakpoint={props.breakpoint} dateLocale={props.dateLocale} >
398-
<SendbirdContextManager {...props} />
447+
<InternalSendbirdProvider {...props} logger={logger} >
448+
<SendbirdContextManager {...props} logger={logger} />
399449
{children}
400450
</InternalSendbirdProvider>
401451
);

src/modules/ChannelSettings/context/ChannelSettingsProvider.tsx

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { useStore } from '../../../hooks/useStore';
77
import { useChannelHandler } from './hooks/useChannelHandler';
88

99
import uuidv4 from '../../../utils/uuid';
10-
import { classnames } from '../../../utils/utils';
10+
import { classnames, deleteNullish } from '../../../utils/utils';
1111
import { createStore } from '../../../utils/storeManager';
1212
import { UserProfileProvider } from '../../../lib/UserProfileContext';
1313
import useSendbird from '../../../lib/Sendbird/context/hooks/useSendbird';
@@ -103,9 +103,27 @@ const ChannelSettingsManager = ({
103103
return null;
104104
};
105105

106-
const createChannelSettingsStore = () => createStore(initialState);
107-
const InternalChannelSettingsProvider = ({ children }) => {
108-
const storeRef = useRef(createChannelSettingsStore());
106+
const createChannelSettingsStore = (props?: Partial<ChannelSettingsState>) => createStore({
107+
...initialState,
108+
...props,
109+
});
110+
111+
const InternalChannelSettingsProvider = (props: ChannelSettingsContextProps) => {
112+
const { children } = props;
113+
114+
const defaultProps: Partial<ChannelSettingsState> = deleteNullish({
115+
channelUrl: props?.channelUrl,
116+
onCloseClick: props?.onCloseClick,
117+
onLeaveChannel: props?.onLeaveChannel,
118+
onChannelModified: props?.onChannelModified,
119+
onBeforeUpdateChannel: props?.onBeforeUpdateChannel,
120+
renderUserListItem: props?.renderUserListItem,
121+
queries: props?.queries,
122+
overrideInviteUser: props?.overrideInviteUser,
123+
});
124+
125+
const storeRef = useRef(createChannelSettingsStore(defaultProps));
126+
109127
return (
110128
<ChannelSettingsContext.Provider value={storeRef.current}>
111129
{children}
@@ -116,7 +134,7 @@ const InternalChannelSettingsProvider = ({ children }) => {
116134
const ChannelSettingsProvider = (props: ChannelSettingsContextProps) => {
117135
const { children, className } = props;
118136
return (
119-
<InternalChannelSettingsProvider>
137+
<InternalChannelSettingsProvider {...props}>
120138
<ChannelSettingsManager {...props} />
121139
<UserProfileProvider {...props}>
122140
<div className={classnames('sendbird-channel-settings', className)}>

src/modules/ChannelSettings/context/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,6 @@ export interface ChannelSettingsState extends CommonChannelSettingsProps {
4242
export interface ChannelSettingsContextProps extends
4343
CommonChannelSettingsProps,
4444
Pick<UserProfileProviderProps, 'renderUserProfile' | 'disableUserProfile'> {
45-
children?: React.ReactElement;
45+
children?: ReactNode;
4646
className?: string;
4747
}

src/modules/CreateChannel/context/CreateChannelProvider.tsx

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { createStore } from '../../../utils/storeManager';
1111
import { useStore } from '../../../hooks/useStore';
1212
import useCreateChannel from './useCreateChannel';
1313
import useSendbird from '../../../lib/Sendbird/context/hooks/useSendbird';
14+
import { deleteNullish } from '../../../utils/utils';
1415

1516
const CreateChannelContext = React.createContext<ReturnType<typeof createStore<CreateChannelState>> | null>(null);
1617

@@ -139,16 +140,31 @@ const CreateChannelProvider: React.FC<CreateChannelProviderProps> = (props: Crea
139140
const { children } = props;
140141

141142
return (
142-
<InternalCreateChannelProvider>
143+
<InternalCreateChannelProvider {...props}>
143144
<CreateChannelManager {...props} />
144145
{children}
145146
</InternalCreateChannelProvider>
146147
);
147148
};
148149

149-
const createCreateChannelStore = () => createStore(initialState);
150-
const InternalCreateChannelProvider: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
151-
const storeRef = useRef(createCreateChannelStore());
150+
const createCreateChannelStore = (props?: Partial<CreateChannelState>) => createStore({
151+
...initialState,
152+
...props,
153+
});
154+
155+
const InternalCreateChannelProvider: React.FC<React.PropsWithChildren<unknown>> = (props: CreateChannelProviderProps) => {
156+
const { children } = props;
157+
158+
const defaultProps: Partial<CreateChannelState> = deleteNullish({
159+
userListQuery: props?.userListQuery,
160+
onCreateChannelClick: props?.onCreateChannelClick,
161+
onChannelCreated: props?.onChannelCreated,
162+
onBeforeCreateChannel: props?.onBeforeCreateChannel,
163+
onCreateChannel: props?.onCreateChannel,
164+
overrideInviteUser: props?.overrideInviteUser,
165+
});
166+
167+
const storeRef = useRef(createCreateChannelStore(defaultProps));
152168

153169
return (
154170
<CreateChannelContext.Provider value={storeRef.current}>

src/modules/GroupChannel/context/GroupChannelProvider.tsx

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import type {
3131
} from './types';
3232
import useSendbird from '../../../lib/Sendbird/context/hooks/useSendbird';
3333
import useDeepCompareEffect from '../../../hooks/useDeepCompareEffect';
34+
import { deleteNullish } from '../../../utils/utils';
3435

3536
const initialState = {
3637
currentChannel: null,
@@ -60,8 +61,48 @@ const initialState = {
6061

6162
export const GroupChannelContext = createContext<ReturnType<typeof createStore<GroupChannelState>> | null>(null);
6263

63-
export const InternalGroupChannelProvider: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
64-
const storeRef = useRef(createStore(initialState));
64+
const createGroupChannelStore = (props?: Partial<GroupChannelState>) => createStore({
65+
...initialState,
66+
...props,
67+
});
68+
69+
export const InternalGroupChannelProvider = (props: GroupChannelProviderProps) => {
70+
const { children } = props;
71+
72+
const defaultProps: Partial<GroupChannelState> = deleteNullish({
73+
channelUrl: props?.channelUrl,
74+
renderUserProfile: props?.renderUserProfile,
75+
disableUserProfile: props?.disableUserProfile,
76+
onUserProfileMessage: props?.onUserProfileMessage,
77+
onStartDirectMessage: props?.onStartDirectMessage,
78+
isReactionEnabled: props?.isReactionEnabled,
79+
isMessageGroupingEnabled: props?.isMessageGroupingEnabled,
80+
isMultipleFilesMessageEnabled: props?.isMultipleFilesMessageEnabled,
81+
showSearchIcon: props?.showSearchIcon,
82+
threadReplySelectType: props?.threadReplySelectType,
83+
disableMarkAsRead: props?.disableMarkAsRead,
84+
scrollBehavior: props?.scrollBehavior,
85+
forceLeftToRightMessageLayout: props?.forceLeftToRightMessageLayout,
86+
startingPoint: props?.startingPoint,
87+
animatedMessageId: props?.animatedMessageId,
88+
onMessageAnimated: props?.onMessageAnimated,
89+
messageListQueryParams: props?.messageListQueryParams,
90+
filterEmojiCategoryIds: props?.filterEmojiCategoryIds,
91+
onBeforeSendUserMessage: props?.onBeforeSendUserMessage,
92+
onBeforeSendFileMessage: props?.onBeforeSendFileMessage,
93+
onBeforeSendVoiceMessage: props?.onBeforeSendVoiceMessage,
94+
onBeforeSendMultipleFilesMessage: props?.onBeforeSendMultipleFilesMessage,
95+
onBeforeUpdateUserMessage: props?.onBeforeUpdateUserMessage,
96+
onBeforeDownloadFileMessage: props?.onBeforeDownloadFileMessage,
97+
onBackClick: props?.onBackClick,
98+
onChatHeaderActionClick: props?.onChatHeaderActionClick,
99+
onReplyInThreadClick: props?.onReplyInThreadClick,
100+
onSearchClick: props?.onSearchClick,
101+
onQuoteMessageClick: props?.onQuoteMessageClick,
102+
renderUserMentionItem: props?.renderUserMentionItem,
103+
});
104+
105+
const storeRef = useRef(createGroupChannelStore(defaultProps));
65106

66107
return (
67108
<GroupChannelContext.Provider value={storeRef.current}>
@@ -319,7 +360,7 @@ const GroupChannelManager :React.FC<React.PropsWithChildren<GroupChannelProvider
319360

320361
const GroupChannelProvider: React.FC<GroupChannelProviderProps> = (props) => {
321362
return (
322-
<InternalGroupChannelProvider>
363+
<InternalGroupChannelProvider {...props}>
323364
<GroupChannelManager {...props}>
324365
<UserProfileProvider {...props}>
325366
{props.children}

src/modules/GroupChannel/context/__tests__/useGroupChannel.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,14 @@ describe('useGroupChannel', () => {
113113
it('provides initial state', () => {
114114
const { result } = renderHook(() => useGroupChannel(), { wrapper });
115115

116-
expect(result.current.state).toEqual(expect.objectContaining({
116+
expect(result.current.state).toMatchObject({
117117
currentChannel: null,
118118
channelUrl: mockChannel.url,
119119
fetchChannelError: null,
120120
quoteMessage: null,
121121
animatedMessageId: null,
122122
isScrollBottomReached: true,
123-
}));
123+
});
124124
});
125125

126126
it('updates channel state', () => {

0 commit comments

Comments
 (0)