Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/lib/Sendbird/context/SendbirdContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,25 @@ export const SendbirdContext = React.createContext<ReturnType<typeof createStore
/**
* Create store for Sendbird context
*/
export const createSendbirdContextStore = () => createStore(initialState);
export const createSendbirdContextStore = (props?: any) => createStore({
Copy link
Collaborator

@chrisallo chrisallo Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 props 타입을 PartialDeep<SendbirdState>로 하는건 어떤가요?

Copy link
Contributor Author

@git-babel git-babel Feb 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

비슷한 부분들 일괄 수정했습니다.
SendbirdContextStore를 제외한 Store들은 값들을 flat하게 가지고 있기 때문에 굳이 PartialDeep을 사용할 필요가 없어 Partial로 대체했습니다.
SendbirdContextStore의 경우 PartialDeep을 사용하게 되면 scrollRef와 같이 object를 value로 가질 경우 이 모두 optional로 취급되어 타입 에러가 발생합니다. 따라서 현재 SendbirdState의 구조에 맞춰 TwoDepthPartial을 추가했습니다.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다만 코드 미관상으로 그렇게 좋지는 않은 것 같습니다. 수정할 부분 있으면 말씀주세요.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

빠르게 도입할 수 있는 대안이 없는것같아서 일단 고 하시죠ㅎㅎ

config: {
...initialState.config,
...props?.config,
},
stores: {
...initialState.stores,
...props?.stores,
},
eventHandlers: {
...initialState.eventHandlers,
...props?.eventHandlers,
},
emojiManager: props?.emojiManager ?? initialState.emojiManager,
utils: {
...initialState.utils,
...props?.utils,
},
});

/**
* A specialized hook for Ssendbird state management
Expand Down
84 changes: 67 additions & 17 deletions src/lib/Sendbird/context/SendbirdProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import React, { ReactElement, useEffect, useMemo, useRef, useState } from 'react
import { useUIKitConfig } from '@sendbird/uikit-tools';

/* Types */
import type { ImageCompressionOptions, SendbirdProviderProps, SendbirdStateConfig } from '../types';
import {
ImageCompressionOptions,
Logger,
SendbirdProviderProps,
SendbirdState,
SendbirdStateConfig,
} from '../types';

/* Providers */
import VoiceMessageProvider from '../../VoiceMessageProvider';
Expand Down Expand Up @@ -33,10 +39,10 @@ import { DEFAULT_MULTIPLE_FILES_MESSAGE_LIMIT, DEFAULT_UPLOAD_SIZE_LIMIT, VOICE_
import { EmojiReactionListRoot, MenuRoot } from '../../../ui/ContextMenu';

import useSendbird from './hooks/useSendbird';
import { SendbirdContext, useSendbirdStore } from './SendbirdContext';
import { createStore } from '../../../utils/storeManager';
import { initialState } from './initialState';
import { createSendbirdContextStore, SendbirdContext, useSendbirdStore } from './SendbirdContext';
import useDeepCompareEffect from '../../../hooks/useDeepCompareEffect';
import { PartialDeep } from '../../../utils/typeHelpers/partialDeep';
import { deleteNullish } from '../../../utils/utils';

/**
* SendbirdContext - Manager
Expand All @@ -49,6 +55,7 @@ const SendbirdContextManager = ({
customWebSocketHost,
configureSession,
theme = 'light',
logger,
config = {},
nickname = '',
colorSet,
Expand All @@ -68,11 +75,10 @@ const SendbirdContextManager = ({
eventHandlers,
htmlTextDirection = 'ltr',
forceLeftToRightMessageLayout = false,
}: SendbirdProviderProps): ReactElement => {
}: SendbirdProviderProps & { logger: Logger }): ReactElement => {
const onStartDirectMessage = _onStartDirectMessage ?? _onUserProfileMessage;
const { logLevel = '', userMention = {}, isREMUnitEnabled = false, pubSub: customPubSub } = config;
const { userMention = {}, isREMUnitEnabled = false, pubSub: customPubSub } = config;
const { isMobile } = useMediaQueryContext();
const [logger, setLogger] = useState(LoggerFactory(logLevel as LogLevel));
const [pubSub] = useState(customPubSub ?? pubSubFactory<PUBSUB_TOPICS, SBUGlobalPubSubTopicPayloadUnion>());

const { state, updateState } = useSendbirdStore();
Expand Down Expand Up @@ -121,11 +127,6 @@ const SendbirdContextManager = ({
actions.disconnect({ logger });
});

// to create a pubsub to communicate between parent and child
useEffect(() => {
setLogger(LoggerFactory(logLevel as LogLevel));
}, [logLevel]);

// should move to reducer
const [currentTheme, setCurrentTheme] = useState(theme);
useEffect(() => {
Expand Down Expand Up @@ -365,8 +366,49 @@ const SendbirdContextManager = ({
return null;
};

const InternalSendbirdProvider = ({ children, stringSet, breakpoint, dateLocale }) => {
const storeRef = useRef(createStore(initialState));
const InternalSendbirdProvider = (props: SendbirdProviderProps & { logger: Logger }) => {
const {
children,
stringSet,
breakpoint,
dateLocale,
} = props;

const defaultProps: PartialDeep<SendbirdState> = deleteNullish({
config: {
renderUserProfile: props?.renderUserProfile,
onStartDirectMessage: props?.onStartDirectMessage,
allowProfileEdit: props?.allowProfileEdit,
appId: props?.appId,
userId: props?.userId,
accessToken: props?.accessToken,
theme: props?.theme,
htmlTextDirection: props?.htmlTextDirection,
forceLeftToRightMessageLayout: props?.forceLeftToRightMessageLayout,
pubSub: props?.config?.pubSub,
logger: props?.logger,
userListQuery: props?.userListQuery,
voiceRecord: {
maxRecordingTime: props?.voiceRecord?.maxRecordingTime ?? VOICE_RECORDER_DEFAULT_MAX,
minRecordingTime: props?.voiceRecord?.minRecordingTime ?? VOICE_RECORDER_DEFAULT_MIN,
},
userMention: {
maxMentionCount: props?.config?.userMention?.maxMentionCount || 10,
maxSuggestionCount: props?.config?.userMention?.maxSuggestionCount || 15,
},
imageCompression: {
compressionRate: 0.7,
outputFormat: 'preserve',
...props?.imageCompression,
},
disableMarkAsDelivered: props?.disableMarkAsDelivered,
isMultipleFilesMessageEnabled: props?.isMultipleFilesMessageEnabled,
},
eventHandlers: props?.eventHandlers,
});

const storeRef = useRef(createSendbirdContextStore(defaultProps));

const localeStringSet = useMemo(() => {
return { ...getStringSet('en'), ...stringSet };
}, [stringSet]);
Expand All @@ -391,11 +433,19 @@ const InternalSendbirdProvider = ({ children, stringSet, breakpoint, dateLocale
};

export const SendbirdContextProvider = (props: SendbirdProviderProps) => {
const { children } = props;
const { children, config } = props;
const logLevel = config?.logLevel;

const [logger, setLogger] = useState(LoggerFactory(logLevel as LogLevel));

// to create a pubsub to communicate between parent and child
useEffect(() => {
setLogger(LoggerFactory(logLevel as LogLevel));
}, [logLevel]);

return (
<InternalSendbirdProvider stringSet={props.stringSet} breakpoint={props.breakpoint} dateLocale={props.dateLocale} >
<SendbirdContextManager {...props} />
<InternalSendbirdProvider {...props} logger={logger} >
<SendbirdContextManager {...props} logger={logger} />
{children}
</InternalSendbirdProvider>
);
Expand Down
29 changes: 24 additions & 5 deletions src/modules/ChannelSettings/context/ChannelSettingsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import { useStore } from '../../../hooks/useStore';
import { useChannelHandler } from './hooks/useChannelHandler';

import uuidv4 from '../../../utils/uuid';
import { classnames } from '../../../utils/utils';
import { classnames, deleteNullish } from '../../../utils/utils';
import { createStore } from '../../../utils/storeManager';
import { UserProfileProvider } from '../../../lib/UserProfileContext';
import useSendbird from '../../../lib/Sendbird/context/hooks/useSendbird';
import useChannelSettings from './useChannelSettings';
import { PartialDeep } from '../../../utils/typeHelpers/partialDeep';

export const ChannelSettingsContext = createContext<ReturnType<typeof createStore<ChannelSettingsState>> | null>(null);

Expand Down Expand Up @@ -103,9 +104,27 @@ const ChannelSettingsManager = ({
return null;
};

const createChannelSettingsStore = () => createStore(initialState);
const InternalChannelSettingsProvider = ({ children }) => {
const storeRef = useRef(createChannelSettingsStore());
const createChannelSettingsStore = (props?: Omit<PartialDeep<ChannelSettingsState>, 'channel'>) => createStore({
...initialState,
...props,
});

const InternalChannelSettingsProvider = (props: ChannelSettingsContextProps) => {
const { children } = props;

const defaultProps: PartialDeep<ChannelSettingsState> = deleteNullish({
channelUrl: props?.channelUrl,
onCloseClick: props?.onCloseClick,
onLeaveChannel: props?.onLeaveChannel,
onChannelModified: props?.onChannelModified,
onBeforeUpdateChannel: props?.onBeforeUpdateChannel,
renderUserListItem: props?.renderUserListItem,
queries: props?.queries,
overrideInviteUser: props?.overrideInviteUser,
});

const storeRef = useRef(createChannelSettingsStore(defaultProps));

return (
<ChannelSettingsContext.Provider value={storeRef.current}>
{children}
Expand All @@ -116,7 +135,7 @@ const InternalChannelSettingsProvider = ({ children }) => {
const ChannelSettingsProvider = (props: ChannelSettingsContextProps) => {
const { children, className } = props;
return (
<InternalChannelSettingsProvider>
<InternalChannelSettingsProvider {...props}>
<ChannelSettingsManager {...props} />
<UserProfileProvider {...props}>
<div className={classnames('sendbird-channel-settings', className)}>
Expand Down
2 changes: 1 addition & 1 deletion src/modules/ChannelSettings/context/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,6 @@ export interface ChannelSettingsState extends CommonChannelSettingsProps {
export interface ChannelSettingsContextProps extends
CommonChannelSettingsProps,
Pick<UserProfileProviderProps, 'renderUserProfile' | 'disableUserProfile'> {
children?: React.ReactElement;
children?: ReactNode;
className?: string;
}
25 changes: 21 additions & 4 deletions src/modules/CreateChannel/context/CreateChannelProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { createStore } from '../../../utils/storeManager';
import { useStore } from '../../../hooks/useStore';
import useCreateChannel from './useCreateChannel';
import useSendbird from '../../../lib/Sendbird/context/hooks/useSendbird';
import { PartialDeep } from '../../../utils/typeHelpers/partialDeep';
import { deleteNullish } from '../../../utils/utils';

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

Expand Down Expand Up @@ -139,16 +141,31 @@ const CreateChannelProvider: React.FC<CreateChannelProviderProps> = (props: Crea
const { children } = props;

return (
<InternalCreateChannelProvider>
<InternalCreateChannelProvider {...props}>
<CreateChannelManager {...props} />
{children}
</InternalCreateChannelProvider>
);
};

const createCreateChannelStore = () => createStore(initialState);
const InternalCreateChannelProvider: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const storeRef = useRef(createCreateChannelStore());
const createCreateChannelStore = (props?: PartialDeep<CreateChannelState>) => createStore({
...initialState,
...props,
});

const InternalCreateChannelProvider: React.FC<React.PropsWithChildren<unknown>> = (props: CreateChannelProviderProps) => {
const { children } = props;

const defaultProps: PartialDeep<CreateChannelState> = deleteNullish({
userListQuery: props?.userListQuery,
onCreateChannelClick: props?.onCreateChannelClick,
onChannelCreated: props?.onChannelCreated,
onBeforeCreateChannel: props?.onBeforeCreateChannel,
onCreateChannel: props?.onCreateChannel,
overrideInviteUser: props?.overrideInviteUser,
});

const storeRef = useRef(createCreateChannelStore(defaultProps));

return (
<CreateChannelContext.Provider value={storeRef.current}>
Expand Down
48 changes: 45 additions & 3 deletions src/modules/GroupChannel/context/GroupChannelProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import type {
} from './types';
import useSendbird from '../../../lib/Sendbird/context/hooks/useSendbird';
import useDeepCompareEffect from '../../../hooks/useDeepCompareEffect';
import { PartialDeep } from '../../../utils/typeHelpers/partialDeep';
import { deleteNullish } from '../../../utils/utils';

const initialState = {
currentChannel: null,
Expand Down Expand Up @@ -60,8 +62,48 @@ const initialState = {

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

export const InternalGroupChannelProvider: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
const storeRef = useRef(createStore(initialState));
const createGroupChannelListStore = (props?: any) => createStore({
...initialState,
...props,
});

export const InternalGroupChannelProvider = (props: GroupChannelProviderProps) => {
const { children } = props;

const defaultProps: PartialDeep<GroupChannelState> = deleteNullish({
channelUrl: props?.channelUrl,
renderUserProfile: props?.renderUserProfile,
disableUserProfile: props?.disableUserProfile,
onUserProfileMessage: props?.onUserProfileMessage,
onStartDirectMessage: props?.onStartDirectMessage,
isReactionEnabled: props?.isReactionEnabled,
isMessageGroupingEnabled: props?.isMessageGroupingEnabled,
isMultipleFilesMessageEnabled: props?.isMultipleFilesMessageEnabled,
showSearchIcon: props?.showSearchIcon,
threadReplySelectType: props?.threadReplySelectType,
disableMarkAsRead: props?.disableMarkAsRead,
scrollBehavior: props?.scrollBehavior,
forceLeftToRightMessageLayout: props?.forceLeftToRightMessageLayout,
startingPoint: props?.startingPoint,
animatedMessageId: props?.animatedMessageId,
onMessageAnimated: props?.onMessageAnimated,
messageListQueryParams: props?.messageListQueryParams,
filterEmojiCategoryIds: props?.filterEmojiCategoryIds,
onBeforeSendUserMessage: props?.onBeforeSendUserMessage,
onBeforeSendFileMessage: props?.onBeforeSendFileMessage,
onBeforeSendVoiceMessage: props?.onBeforeSendVoiceMessage,
onBeforeSendMultipleFilesMessage: props?.onBeforeSendMultipleFilesMessage,
onBeforeUpdateUserMessage: props?.onBeforeUpdateUserMessage,
onBeforeDownloadFileMessage: props?.onBeforeDownloadFileMessage,
onBackClick: props?.onBackClick,
onChatHeaderActionClick: props?.onChatHeaderActionClick,
onReplyInThreadClick: props?.onReplyInThreadClick,
onSearchClick: props?.onSearchClick,
onQuoteMessageClick: props?.onQuoteMessageClick,
renderUserMentionItem: props?.renderUserMentionItem,
});

const storeRef = useRef(createGroupChannelListStore(defaultProps));

return (
<GroupChannelContext.Provider value={storeRef.current}>
Expand Down Expand Up @@ -319,7 +361,7 @@ const GroupChannelManager :React.FC<React.PropsWithChildren<GroupChannelProvider

const GroupChannelProvider: React.FC<GroupChannelProviderProps> = (props) => {
return (
<InternalGroupChannelProvider>
<InternalGroupChannelProvider {...props}>
<GroupChannelManager {...props}>
<UserProfileProvider {...props}>
{props.children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,14 @@ describe('useGroupChannel', () => {
it('provides initial state', () => {
const { result } = renderHook(() => useGroupChannel(), { wrapper });

expect(result.current.state).toEqual(expect.objectContaining({
expect(result.current.state).toMatchObject({
currentChannel: null,
channelUrl: mockChannel.url,
fetchChannelError: null,
quoteMessage: null,
animatedMessageId: null,
isScrollBottomReached: true,
}));
});
});

it('updates channel state', () => {
Expand Down
Loading