diff --git a/apps/meteor/client/components/Header/Header.stories.tsx b/apps/meteor/client/components/Header/Header.stories.tsx index 0f7905c3d0a6..a8742be2b807 100644 --- a/apps/meteor/client/components/Header/Header.stories.tsx +++ b/apps/meteor/client/components/Header/Header.stories.tsx @@ -30,8 +30,9 @@ export default { value={{ hasPrivateAccess: true, isLoading: false, - querySetting: (_id) => ({ - getCurrentValue: () => ({ + querySetting: (_id) => [ + () => () => undefined, + () => ({ _id, type: 'action', value: '', @@ -44,12 +45,8 @@ export default { sorter: 1, ts: new Date(), }), - subscribe: () => () => undefined, - }), - querySettings: () => ({ - getCurrentValue: () => [], - subscribe: () => () => undefined, - }), + ], + querySettings: () => [() => () => undefined, () => []], dispatch: async () => undefined, }} > diff --git a/apps/meteor/client/components/Omnichannel/Tags.tsx b/apps/meteor/client/components/Omnichannel/Tags.tsx index ad2d515f4373..6887bfe07e4b 100644 --- a/apps/meteor/client/components/Omnichannel/Tags.tsx +++ b/apps/meteor/client/components/Omnichannel/Tags.tsx @@ -2,11 +2,10 @@ import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ChangeEvent, ReactElement, useState } from 'react'; -import { useSubscription } from 'use-subscription'; import { AsyncStatePhase } from '../../hooks/useAsyncState'; import { useEndpointData } from '../../hooks/useEndpointData'; -import { formsSubscription } from '../../views/omnichannel/additionalForms'; +import { useFormsSubscription } from '../../views/omnichannel/additionalForms'; import { FormSkeleton } from './Skeleton'; const Tags = ({ @@ -21,7 +20,7 @@ const Tags = ({ tagRequired?: boolean; }): ReactElement => { const t = useTranslation(); - const forms = useSubscription(formsSubscription); + const forms = useFormsSubscription() as any; const { value: tagsResult, phase: stateTags } = useEndpointData('/v1/livechat/tags.list'); diff --git a/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/context/OmnichannelRoomIconContext.tsx b/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/context/OmnichannelRoomIconContext.tsx index 7c825e1d54f9..b51a962d8e92 100644 --- a/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/context/OmnichannelRoomIconContext.tsx +++ b/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/context/OmnichannelRoomIconContext.tsx @@ -1,31 +1,26 @@ import { createContext, useMemo, useContext } from 'react'; -import { useSubscription, Unsubscribe } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { AsyncState } from '../../../../lib/asyncState/AsyncState'; import { AsyncStatePhase } from '../../../../lib/asyncState/AsyncStatePhase'; type IOmnichannelRoomIconContext = { - queryIcon( - app: string, - icon: string, - ): { - getCurrentValue: () => AsyncState; - subscribe: (callback: () => void) => Unsubscribe; - }; + queryIcon(app: string, icon: string): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => AsyncState]; }; export const OmnichannelRoomIconContext = createContext({ - queryIcon: () => ({ - getCurrentValue: (): AsyncState => ({ + queryIcon: () => [ + (): (() => void) => (): void => undefined, + (): AsyncState => ({ phase: AsyncStatePhase.LOADING, value: undefined, error: undefined, }), - subscribe: (): Unsubscribe => (): void => undefined, - }), + ], }); export const useOmnichannelRoomIcon = (app: string, icon: string): AsyncState => { const { queryIcon } = useContext(OmnichannelRoomIconContext); - return useSubscription(useMemo(() => queryIcon(app, icon), [app, queryIcon, icon])); + const [subscribe, getSnapshot] = useMemo(() => queryIcon(app, icon), [app, queryIcon, icon]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider.tsx b/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider.tsx index 0e3ca29581d3..3171ebf34759 100644 --- a/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider.tsx +++ b/apps/meteor/client/components/RoomIcon/OmnichannelRoomIcon/provider/OmnichannelRoomIconProvider.tsx @@ -1,28 +1,37 @@ -import React, { FC, useMemo } from 'react'; +import React, { FC, useCallback, useMemo } from 'react'; import { createPortal } from 'react-dom'; -import { useSubscription, Subscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { AsyncState } from '../../../../lib/asyncState/AsyncState'; import { AsyncStatePhase } from '../../../../lib/asyncState/AsyncStatePhase'; import { OmnichannelRoomIconContext } from '../context/OmnichannelRoomIconContext'; import OmnichannelRoomIcon from '../lib/OmnichannelRoomIcon'; +let icons = Array.from(OmnichannelRoomIcon.icons.values()); + export const OmnichannelRoomIconProvider: FC = ({ children }) => { - const svgIcons = useSubscription( - useMemo( - () => ({ - getCurrentValue: (): string[] => Array.from(OmnichannelRoomIcon.icons.values()), - subscribe: (callback): (() => void) => OmnichannelRoomIcon.on('change', callback), - }), + const svgIcons = useSyncExternalStore( + useCallback( + (callback): (() => void) => + OmnichannelRoomIcon.on('change', () => { + icons = Array.from(OmnichannelRoomIcon.icons.values()); + callback(); + }), [], ), + (): string[] => icons, ); + return ( ({ - queryIcon: (app: string, iconName: string): Subscription> => ({ - getCurrentValue: (): AsyncState => { + queryIcon: ( + app: string, + iconName: string, + ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => AsyncState] => [ + (callback): (() => void) => OmnichannelRoomIcon.on(`${app}-${iconName}`, callback), + (): AsyncState => { const icon = OmnichannelRoomIcon.get(app, iconName); if (!icon) { @@ -39,8 +48,7 @@ export const OmnichannelRoomIconProvider: FC = ({ children }) => { error: undefined, }; }, - subscribe: (callback): (() => void) => OmnichannelRoomIcon.on(`${app}-${iconName}`, callback), - }), + ], }), [], )} diff --git a/apps/meteor/client/contexts/CallContext.ts b/apps/meteor/client/contexts/CallContext.ts index f5c02d0292a9..6c1845c58e12 100644 --- a/apps/meteor/client/contexts/CallContext.ts +++ b/apps/meteor/client/contexts/CallContext.ts @@ -1,7 +1,7 @@ import type { IVoipRoom } from '@rocket.chat/core-typings'; import { ICallerInfo, VoIpCallerInfo } from '@rocket.chat/core-typings'; -import { createContext, useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { createContext, useCallback, useContext } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { VoIPUser } from '../lib/voip/VoIPUser'; @@ -89,20 +89,17 @@ export const useCallerInfo = (): VoIpCallerInfo => { throw new Error('useCallerInfo only if Calls are enabled and ready'); } const { voipClient } = context; - const subscription = useMemo( - () => ({ - getCurrentValue: (): VoIpCallerInfo => voipClient.callerInfo, - subscribe: (callback: () => void): (() => void) => { - voipClient.on('stateChanged', callback); - - return (): void => { - voipClient.off('stateChanged', callback); - }; - }, - }), + const subscribe = useCallback( + (callback: () => void): (() => void) => { + voipClient.on('stateChanged', callback); + + return (): void => { + voipClient.off('stateChanged', callback); + }; + }, [voipClient], ); - return useSubscription(subscription); + return useSyncExternalStore(subscribe, (): VoIpCallerInfo => voipClient.callerInfo); }; export const useCallCreateRoom = (): CallContextReady['createRoom'] => { diff --git a/apps/meteor/client/hooks/usePresence.ts b/apps/meteor/client/hooks/usePresence.ts index e02fd9c2cc32..488ae58cab86 100644 --- a/apps/meteor/client/hooks/usePresence.ts +++ b/apps/meteor/client/hooks/usePresence.ts @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useCallback } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { Presence, UserPresence } from '../lib/presence'; @@ -13,18 +13,17 @@ type Presence = 'online' | 'offline' | 'busy' | 'away' | 'loading'; * @public */ export const usePresence = (uid: string | undefined): UserPresence | undefined => { - const subscription = useMemo( - () => ({ - getCurrentValue: (): UserPresence | undefined => (uid ? Presence.store.get(uid) : undefined), - subscribe: (callback: any): any => { - uid && Presence.listen(uid, callback); - return (): void => { - uid && Presence.stop(uid, callback); - }; - }, - }), + const subscribe = useCallback( + (callback: any): any => { + uid && Presence.listen(uid, callback); + return (): void => { + uid && Presence.stop(uid, callback); + }; + }, [uid], ); - return useSubscription(subscription); + const getSnapshot = (): UserPresence | undefined => (uid ? Presence.store.get(uid) : undefined); + + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/apps/meteor/client/hooks/useReactiveValue.ts b/apps/meteor/client/hooks/useReactiveValue.ts index 1ffb609429d3..8c50037121c1 100644 --- a/apps/meteor/client/hooks/useReactiveValue.ts +++ b/apps/meteor/client/hooks/useReactiveValue.ts @@ -1,10 +1,10 @@ import { Tracker } from 'meteor/tracker'; import { useMemo } from 'react'; -import { Subscription, Unsubscribe, useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; export const useReactiveValue = (computeCurrentValue: () => T): T => { - const subscription: Subscription = useMemo(() => { - const callbacks = new Set(); + const [subscribe, getSnapshot] = useMemo(() => { + const callbacks = new Set<() => void>(); let currentValue: T; @@ -15,9 +15,8 @@ export const useReactiveValue = (computeCurrentValue: () => T): T => { }); }); - return { - getCurrentValue: (): T => currentValue, - subscribe: (callback): Unsubscribe => { + return [ + (callback: () => void): (() => void) => { callbacks.add(callback); return (): void => { @@ -28,8 +27,9 @@ export const useReactiveValue = (computeCurrentValue: () => T): T => { } }; }, - }; + (): T => currentValue, + ]; }, [computeCurrentValue]); - return useSubscription(subscription); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/apps/meteor/client/hooks/useUserData.ts b/apps/meteor/client/hooks/useUserData.ts index 15e2e040741d..4621cf895122 100644 --- a/apps/meteor/client/hooks/useUserData.ts +++ b/apps/meteor/client/hooks/useUserData.ts @@ -1,5 +1,5 @@ -import { useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useCallback } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { UserPresence, Presence } from '../lib/presence'; @@ -11,18 +11,17 @@ import { UserPresence, Presence } from '../lib/presence'; * @public */ export const useUserData = (uid: string): UserPresence | undefined => { - const subscription = useMemo( - () => ({ - getCurrentValue: (): UserPresence | undefined => Presence.store.get(uid), - subscribe: (callback: any): any => { - Presence.listen(uid, callback); - return (): void => { - Presence.stop(uid, callback); - }; - }, - }), + const subscription = useCallback( + (callback: () => void): (() => void) => { + Presence.listen(uid, callback); + return (): void => { + Presence.stop(uid, callback); + }; + }, [uid], ); - return useSubscription(subscription); + const getSnapshot = (): UserPresence | undefined => Presence.store.get(uid); + + return useSyncExternalStore(subscription, getSnapshot); }; diff --git a/apps/meteor/client/lib/RoomManager.ts b/apps/meteor/client/lib/RoomManager.ts index 31a0d5f0b78a..f05bde279d42 100644 --- a/apps/meteor/client/lib/RoomManager.ts +++ b/apps/meteor/client/lib/RoomManager.ts @@ -1,8 +1,8 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; import { useUserId, useUserRoom, useUserSubscription } from '@rocket.chat/ui-contexts'; -import { useEffect, useMemo } from 'react'; -import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; +import { useCallback, useEffect } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { RoomHistoryManager } from '../../app/ui-utils/client/lib/RoomHistoryManager'; import { useAsyncState } from '../hooks/useAsyncState'; @@ -129,19 +129,15 @@ export const RoomManager = new (class RoomManager extends Emitter<{ } })(); -const subscribeVisitedRooms: Subscription = { - getCurrentValue: () => RoomManager.visitedRooms(), - subscribe(callback) { - return RoomManager.on('changed', callback); - }, -}; +const subscribeVisitedRooms = [ + (callback: () => void): (() => void) => RoomManager.on('changed', callback), + (): IRoom['_id'][] => RoomManager.visitedRooms(), +] as const; -const subscribeOpenedRoom: Subscription = { - getCurrentValue: () => RoomManager.opened, - subscribe(callback) { - return RoomManager.on('opened', callback); - }, -}; +const subscribeOpenedRoom = [ + (callback: () => void): (() => void) => RoomManager.on('opened', callback), + (): IRoom['_id'] | undefined => RoomManager.opened, +] as const; const fields = {}; @@ -165,25 +161,19 @@ export const useHandleRoom = (rid: IRoom['_id']): AsyncState return state; }; -export const useVisitedRooms = (): IRoom['_id'][] => useSubscription(subscribeVisitedRooms); +export const useVisitedRooms = (): IRoom['_id'][] => useSyncExternalStore(...subscribeVisitedRooms); -export const useOpenedRoom = (): IRoom['_id'] | undefined => useSubscription(subscribeOpenedRoom); +export const useOpenedRoom = (): IRoom['_id'] | undefined => useSyncExternalStore(...subscribeOpenedRoom); export const useRoomStore = (rid: IRoom['_id']): RoomStore => { - const subscribeStore: Subscription = useMemo( - () => ({ - getCurrentValue: (): RoomStore | undefined => RoomManager.getStore(rid), - subscribe(callback): Unsubscribe { - return RoomManager.on('changed', callback); - }, - }), - [rid], - ); - - const store = useSubscription(subscribeStore); + const subscribe = useCallback((callback: () => void): (() => void) => RoomManager.on('changed', callback), []); + const getSnapshot = (): RoomStore | undefined => RoomManager.getStore(rid); + + const store = useSyncExternalStore(subscribe, getSnapshot); if (!store) { throw new Error('Something wrong'); } + return store; }; diff --git a/apps/meteor/client/lib/appLayout.ts b/apps/meteor/client/lib/appLayout.ts index 4f2f3a519b7b..a8a8be725401 100644 --- a/apps/meteor/client/lib/appLayout.ts +++ b/apps/meteor/client/lib/appLayout.ts @@ -1,15 +1,14 @@ import { Emitter } from '@rocket.chat/emitter'; import { ReactElement } from 'react'; -import { Subscription, Unsubscribe } from 'use-subscription'; type AppLayoutDescriptor = ReactElement | null; -class AppLayoutSubscription extends Emitter<{ update: void }> implements Subscription { +class AppLayoutSubscription extends Emitter<{ update: void }> { private descriptor: AppLayoutDescriptor = null; - getCurrentValue = (): AppLayoutDescriptor => this.descriptor; + getSnapshot = (): AppLayoutDescriptor => this.descriptor; - subscribe = (callback: () => void): Unsubscribe => this.on('update', callback); + subscribe = (onStoreChange: () => void): (() => void) => this.on('update', onStoreChange); setCurrentValue(descriptor: AppLayoutDescriptor): void { this.descriptor = descriptor; diff --git a/apps/meteor/client/lib/banners.ts b/apps/meteor/client/lib/banners.ts index eb8a06850fa9..02df87e95eca 100644 --- a/apps/meteor/client/lib/banners.ts +++ b/apps/meteor/client/lib/banners.ts @@ -2,7 +2,6 @@ import { UiKitBannerPayload } from '@rocket.chat/core-typings'; import { Emitter } from '@rocket.chat/emitter'; import { Icon } from '@rocket.chat/fuselage'; import { ComponentProps } from 'react'; -import { Subscription } from 'use-subscription'; export type LegacyBannerPayload = { id: string; @@ -27,10 +26,10 @@ const emitter = new Emitter<{ 'update-first': undefined; }>(); -export const firstSubscription: Subscription = { - getCurrentValue: () => queue[0] ?? null, - subscribe: (callback) => emitter.on('update-first', callback), -}; +export const firstSubscription = [ + (callback: () => void): (() => void) => emitter.on('update-first', callback), + (): BannerPayload | null => queue[0] ?? null, +] as const; export const open = (payload: BannerPayload): void => { let index = queue.findIndex((_payload) => { diff --git a/apps/meteor/client/lib/createSidebarItems.ts b/apps/meteor/client/lib/createSidebarItems.ts index 8b1c7db9e928..b773e733a586 100644 --- a/apps/meteor/client/lib/createSidebarItems.ts +++ b/apps/meteor/client/lib/createSidebarItems.ts @@ -1,5 +1,4 @@ import { IconProps } from '@rocket.chat/fuselage'; -import type { Subscription } from 'use-subscription'; export type SidebarItem = { i18nLabel: string; @@ -17,19 +16,19 @@ export const createSidebarItems = ( ): { registerSidebarItem: (item: SidebarItem) => void; unregisterSidebarItem: (i18nLabel: SidebarItem['i18nLabel']) => void; - itemsSubscription: Subscription; + getSidebarItems: () => SidebarItem[]; + subscribeToSidebarItems: (callback: () => void) => () => void; } => { const items = initialItems; let updateCb: () => void = () => undefined; - const itemsSubscription: Subscription = { - subscribe: (cb) => { - updateCb = cb; - return (): void => { - updateCb = (): void => undefined; - }; - }, - getCurrentValue: () => items, + const getSidebarItems = (): SidebarItem[] => items; + + const subscribeToSidebarItems = (cb: () => void): (() => void) => { + updateCb = cb; + return (): void => { + updateCb = (): void => undefined; + }; }; const registerSidebarItem = (item: SidebarItem): void => { @@ -46,6 +45,7 @@ export const createSidebarItems = ( return { registerSidebarItem, unregisterSidebarItem, - itemsSubscription, + getSidebarItems, + subscribeToSidebarItems, }; }; diff --git a/apps/meteor/client/lib/createValueSubscription.ts b/apps/meteor/client/lib/createValueSubscription.ts deleted file mode 100644 index 9702f748e93a..000000000000 --- a/apps/meteor/client/lib/createValueSubscription.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Emitter } from '@rocket.chat/emitter'; -import { Subscription, Unsubscribe } from 'use-subscription'; - -type ValueSubscription = Subscription & { - setCurrentValue: (value: T) => void; -}; - -export const createValueSubscription = (initialValue: T): ValueSubscription => { - let value: T = initialValue; - const emitter = new Emitter<{ - update: undefined; - }>(); - - return { - getCurrentValue: (): T => value, - setCurrentValue: (_value: T): void => { - value = _value; - emitter.emit('update'); - }, - subscribe: (callback): Unsubscribe => emitter.on('update', callback), - }; -}; diff --git a/apps/meteor/client/lib/portals/blazePortals.ts b/apps/meteor/client/lib/portals/blazePortals.ts index 0ddf73026744..ab30c96b634b 100644 --- a/apps/meteor/client/lib/portals/blazePortals.ts +++ b/apps/meteor/client/lib/portals/blazePortals.ts @@ -1,25 +1,27 @@ import { Emitter } from '@rocket.chat/emitter'; import { Random } from 'meteor/random'; import type { ReactNode } from 'react'; -import type { Subscription, Unsubscribe } from 'use-subscription'; type BlazePortalEntry = { key: string; node: ReactNode; }; -class BlazePortalsSubscriptions extends Emitter<{ update: void }> implements Subscription { +class BlazePortalsSubscriptions extends Emitter<{ update: void }> { private map = new Map(); - getCurrentValue = (): BlazePortalEntry[] => Array.from(this.map.values()); + private cache = Array.from(this.map.values()); - subscribe = (callback: () => void): Unsubscribe => this.on('update', callback); + getSnapshot = (): BlazePortalEntry[] => this.cache; + + subscribe = (onStoreChange: () => void): (() => void) => this.on('update', onStoreChange); register = (template: Blaze.TemplateInstance, node: ReactNode): void => { const entry = this.map.get(template); if (!entry) { this.map.set(template, { key: Random.id(), node }); + this.cache = Array.from(this.map.values()); this.emit('update'); return; } @@ -29,11 +31,13 @@ class BlazePortalsSubscriptions extends Emitter<{ update: void }> implements Sub } this.map.set(template, { ...entry, node }); + this.cache = Array.from(this.map.values()); this.emit('update'); }; unregister = (template: Blaze.TemplateInstance): void => { if (this.map.delete(template)) { + this.cache = Array.from(this.map.values()); this.emit('update'); } }; diff --git a/apps/meteor/client/lib/portals/portalsSubscription.ts b/apps/meteor/client/lib/portals/portalsSubscription.ts index c17af505c4fc..b72aa8f3ded6 100644 --- a/apps/meteor/client/lib/portals/portalsSubscription.ts +++ b/apps/meteor/client/lib/portals/portalsSubscription.ts @@ -1,14 +1,15 @@ import { Emitter } from '@rocket.chat/emitter'; import { Random } from 'meteor/random'; import type { ReactElement } from 'react'; -import type { Subscription, Unsubscribe } from 'use-subscription'; type SubscribedPortal = { portal: ReactElement; key: string; }; -type PortalsSubscription = Subscription & { +type PortalsSubscription = { + subscribe: (callback: () => void) => () => void; + getSnapshot: () => SubscribedPortal[]; has: (key: unknown) => boolean; set: (key: unknown, portal: ReactElement) => void; delete: (key: unknown) => void; @@ -16,17 +17,20 @@ type PortalsSubscription = Subscription & { const createPortalsSubscription = (): PortalsSubscription => { const portalsMap = new Map(); + let portals = Array.from(portalsMap.values()); const emitter = new Emitter<{ update: void }>(); return { - getCurrentValue: (): SubscribedPortal[] => Array.from(portalsMap.values()), - subscribe: (callback): Unsubscribe => emitter.on('update', callback), + getSnapshot: (): SubscribedPortal[] => portals, + subscribe: (callback): (() => void) => emitter.on('update', callback), delete: (key): void => { portalsMap.delete(key); + portals = Array.from(portalsMap.values()); emitter.emit('update'); }, set: (key, portal): void => { portalsMap.set(key, { portal, key: Random.id() }); + portals = Array.from(portalsMap.values()); emitter.emit('update'); }, has: (key): boolean => portalsMap.has(key), diff --git a/apps/meteor/client/providers/RouterProvider.tsx b/apps/meteor/client/providers/RouterProvider.tsx index b6d4b83cf260..91f9711b4587 100644 --- a/apps/meteor/client/providers/RouterProvider.tsx +++ b/apps/meteor/client/providers/RouterProvider.tsx @@ -2,13 +2,11 @@ import { RouterContext, RouterContextValue } from '@rocket.chat/ui-contexts'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Tracker } from 'meteor/tracker'; import React, { FC } from 'react'; -import { Subscription, Unsubscribe } from 'use-subscription'; -const createSubscription = function (getValue: () => T): Subscription { +const createSubscription = function (getValue: () => T): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => T] { let currentValue = Tracker.nonreactive(getValue); - return { - getCurrentValue: (): T => currentValue, - subscribe: (callback: () => void): Unsubscribe => { + return [ + (callback: () => void): (() => void) => { const computation = Tracker.autorun(() => { currentValue = getValue(); callback(); @@ -18,7 +16,8 @@ const createSubscription = function (getValue: () => T): Subscription { computation.stop(); }; }, - }; + (): T => currentValue, + ]; }; const queryRoutePath = ( diff --git a/apps/meteor/client/providers/createReactiveSubscriptionFactory.ts b/apps/meteor/client/providers/createReactiveSubscriptionFactory.ts index cf9e92695b2b..4dead8e2b29f 100644 --- a/apps/meteor/client/providers/createReactiveSubscriptionFactory.ts +++ b/apps/meteor/client/providers/createReactiveSubscriptionFactory.ts @@ -1,16 +1,15 @@ import { Tracker } from 'meteor/tracker'; -import { Subscription, Unsubscribe } from 'use-subscription'; interface ISubscriptionFactory { - (...args: any[]): Subscription; + (...args: any[]): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => T]; } export const createReactiveSubscriptionFactory = (computeCurrentValueWith: (...args: any[]) => T): ISubscriptionFactory => - (...args: any[]): Subscription => { + (...args: any[]): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => T] => { const computeCurrentValue = (): T => computeCurrentValueWith(...args); - const callbacks = new Set(); + const callbacks = new Set<() => void>(); let currentValue = computeCurrentValue(); @@ -24,9 +23,8 @@ export const createReactiveSubscriptionFactory = }); }, 0); - return { - getCurrentValue: (): T => currentValue, - subscribe: (callback): Unsubscribe => { + return [ + (callback): (() => void) => { callbacks.add(callback); return (): void => { @@ -39,5 +37,6 @@ export const createReactiveSubscriptionFactory = } }; }, - }; + (): T => currentValue, + ]; }; diff --git a/apps/meteor/client/sidebar/Sidebar.stories.tsx b/apps/meteor/client/sidebar/Sidebar.stories.tsx index 8a9e70d9c20e..6db4e4243519 100644 --- a/apps/meteor/client/sidebar/Sidebar.stories.tsx +++ b/apps/meteor/client/sidebar/Sidebar.stories.tsx @@ -31,14 +31,8 @@ const settings: Record = { const settingContextValue: ContextType = { hasPrivateAccess: true, isLoading: false, - querySetting: (_id) => ({ - getCurrentValue: () => settings[_id], - subscribe: () => () => undefined, - }), - querySettings: () => ({ - getCurrentValue: () => [], - subscribe: () => () => undefined, - }), + querySetting: (_id) => [() => () => undefined, () => settings[_id]], + querySettings: () => [() => () => undefined, () => []], dispatch: async () => undefined, }; @@ -87,24 +81,15 @@ const userContextValue: ContextType = { roles: ['admin'], type: 'user', }, - queryPreference: (pref: string | ObjectId, defaultValue: T) => ({ - getCurrentValue: () => (typeof pref === 'string' ? (userPreferences[pref] as T) : defaultValue), - subscribe: () => () => undefined, - }), - querySubscriptions: () => ({ - getCurrentValue: () => subscriptions, - subscribe: () => () => undefined, - }), - querySubscription: () => ({ - getCurrentValue: () => undefined, - subscribe: () => () => undefined, - }), + queryPreference: (pref: string | ObjectId, defaultValue: T) => [ + () => () => undefined, + () => (typeof pref === 'string' ? (userPreferences[pref] as T) : defaultValue), + ], + querySubscriptions: () => [() => () => undefined, () => subscriptions], + querySubscription: () => [() => () => undefined, () => undefined], loginWithPassword: () => Promise.resolve(undefined), logout: () => Promise.resolve(undefined), - queryRoom: () => ({ - getCurrentValue: () => undefined, - subscribe: () => () => undefined, - }), + queryRoom: () => [() => () => undefined, () => undefined], }; export const Sidebar: Story = () => ( diff --git a/apps/meteor/client/views/account/AccountSidebar.tsx b/apps/meteor/client/views/account/AccountSidebar.tsx index aff5500e00f8..37662e5e8b82 100644 --- a/apps/meteor/client/views/account/AccountSidebar.tsx +++ b/apps/meteor/client/views/account/AccountSidebar.tsx @@ -1,17 +1,17 @@ import { useRoutePath, useCurrentRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { memo, ReactElement, useCallback, useEffect } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; -import { itemsSubscription } from '.'; import { menu, SideNav } from '../../../app/ui-utils/client'; import Sidebar from '../../components/Sidebar'; import { isLayoutEmbedded } from '../../lib/utils/isLayoutEmbedded'; import SettingsProvider from '../../providers/SettingsProvider'; +import { getAccountSidebarItems, subscribeToAccountSidebarItems } from './sidebarItems'; const AccountSidebar = (): ReactElement => { const t = useTranslation(); - const items = useSubscription(itemsSubscription); + const items = useSyncExternalStore(subscribeToAccountSidebarItems, getAccountSidebarItems); const closeFlex = useCallback(() => { if (isLayoutEmbedded()) { diff --git a/apps/meteor/client/views/account/index.ts b/apps/meteor/client/views/account/index.ts index 01c25a32a809..8fc7e4da2521 100644 --- a/apps/meteor/client/views/account/index.ts +++ b/apps/meteor/client/views/account/index.ts @@ -1,2 +1,2 @@ export { registerAccountRoute } from './routes'; -export { registerAccountSidebarItem, unregisterSidebarItem, itemsSubscription } from './sidebarItems'; +export { registerAccountSidebarItem, unregisterSidebarItem } from './sidebarItems'; diff --git a/apps/meteor/client/views/account/sidebarItems.ts b/apps/meteor/client/views/account/sidebarItems.ts index 8ce98bc85e86..77ed0fa01267 100644 --- a/apps/meteor/client/views/account/sidebarItems.ts +++ b/apps/meteor/client/views/account/sidebarItems.ts @@ -5,7 +5,8 @@ import { createSidebarItems } from '../../lib/createSidebarItems'; export const { registerSidebarItem: registerAccountSidebarItem, unregisterSidebarItem, - itemsSubscription, + getSidebarItems: getAccountSidebarItems, + subscribeToSidebarItems: subscribeToAccountSidebarItems, } = createSidebarItems([ { href: 'preferences', diff --git a/apps/meteor/client/views/admin/EditableSettingsContext.ts b/apps/meteor/client/views/admin/EditableSettingsContext.ts index 2c74b679fc9e..c8898f09a93d 100644 --- a/apps/meteor/client/views/admin/EditableSettingsContext.ts +++ b/apps/meteor/client/views/admin/EditableSettingsContext.ts @@ -1,7 +1,7 @@ import { ISettingBase, SectionName, SettingId, GroupId, TabId, ISettingColor } from '@rocket.chat/core-typings'; import { SettingsContextQuery } from '@rocket.chat/ui-contexts'; import { createContext, useContext, useMemo } from 'react'; -import { useSubscription, Subscription, Unsubscribe } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; export type EditableSetting = (ISettingBase | ISettingColor) & { disabled: boolean; @@ -14,58 +14,53 @@ export type EditableSettingsContextQuery = SettingsContextQuery & { }; export type EditableSettingsContextValue = { - readonly queryEditableSetting: (_id: SettingId) => Subscription; - readonly queryEditableSettings: (query: EditableSettingsContextQuery) => Subscription; - readonly queryGroupSections: (_id: GroupId, tab?: TabId) => Subscription; - readonly queryGroupTabs: (_id: GroupId) => Subscription; + readonly queryEditableSetting: ( + _id: SettingId, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => EditableSetting | undefined]; + readonly queryEditableSettings: ( + query: EditableSettingsContextQuery, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => EditableSetting[]]; + readonly queryGroupSections: ( + _id: GroupId, + tab?: TabId, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => SectionName[]]; + readonly queryGroupTabs: (_id: GroupId) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => TabId[]]; readonly dispatch: (changes: Partial[]) => void; }; export const EditableSettingsContext = createContext({ - queryEditableSetting: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryEditableSettings: () => ({ - getCurrentValue: (): EditableSetting[] => [], - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryGroupSections: () => ({ - getCurrentValue: (): SectionName[] => [], - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryGroupTabs: () => ({ - getCurrentValue: (): TabId[] => [], - subscribe: (): Unsubscribe => (): void => undefined, - }), + queryEditableSetting: () => [(): (() => void) => (): void => undefined, (): undefined => undefined], + queryEditableSettings: () => [(): (() => void) => (): void => undefined, (): EditableSetting[] => []], + queryGroupSections: () => [(): (() => void) => (): void => undefined, (): SectionName[] => []], + queryGroupTabs: () => [(): (() => void) => (): void => undefined, (): TabId[] => []], dispatch: () => undefined, }); export const useEditableSetting = (_id: SettingId): EditableSetting | undefined => { const { queryEditableSetting } = useContext(EditableSettingsContext); - const subscription = useMemo(() => queryEditableSetting(_id), [queryEditableSetting, _id]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryEditableSetting(_id), [queryEditableSetting, _id]); + return useSyncExternalStore(subscribe, getSnapshot); }; export const useEditableSettings = (query?: EditableSettingsContextQuery): EditableSetting[] => { const { queryEditableSettings } = useContext(EditableSettingsContext); - const subscription = useMemo(() => queryEditableSettings(query ?? {}), [queryEditableSettings, query]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryEditableSettings(query ?? {}), [queryEditableSettings, query]); + return useSyncExternalStore(subscribe, getSnapshot); }; export const useEditableSettingsGroupSections = (_id: SettingId, tab?: TabId): SectionName[] => { const { queryGroupSections } = useContext(EditableSettingsContext); - const subscription = useMemo(() => queryGroupSections(_id, tab), [queryGroupSections, _id, tab]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryGroupSections(_id, tab), [queryGroupSections, _id, tab]); + return useSyncExternalStore(subscribe, getSnapshot); }; export const useEditableSettingsGroupTabs = (_id: SettingId): TabId[] => { const { queryGroupTabs } = useContext(EditableSettingsContext); - const subscription = useMemo(() => queryGroupTabs(_id), [queryGroupTabs, _id]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryGroupTabs(_id), [queryGroupTabs, _id]); + return useSyncExternalStore(subscribe, getSnapshot); }; export const useEditableSettingsDispatch = (): ((changes: Partial[]) => void) => diff --git a/apps/meteor/client/views/admin/sidebar/AdminSidebarPages.tsx b/apps/meteor/client/views/admin/sidebar/AdminSidebarPages.tsx index e188e396bd7d..664c18838c63 100644 --- a/apps/meteor/client/views/admin/sidebar/AdminSidebarPages.tsx +++ b/apps/meteor/client/views/admin/sidebar/AdminSidebarPages.tsx @@ -1,10 +1,10 @@ import { Box } from '@rocket.chat/fuselage'; import React, { memo, FC } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import SidebarItemsAssembler from '../../../components/Sidebar/SidebarItemsAssembler'; import { useUpgradeTabParams } from '../../hooks/useUpgradeTabParams'; -import { itemsSubscription } from '../sidebarItems'; +import { subscribeToAdminSidebarItems, getAdminSidebarItems } from '../sidebarItems'; import UpgradeTab from './UpgradeTab'; type AdminSidebarPagesProps = { @@ -12,7 +12,8 @@ type AdminSidebarPagesProps = { }; const AdminSidebarPages: FC = ({ currentPath }) => { - const items = useSubscription(itemsSubscription); + const items = useSyncExternalStore(subscribeToAdminSidebarItems, getAdminSidebarItems); + const { tabType, trialEndDate, isLoading } = useUpgradeTabParams(); return ( diff --git a/apps/meteor/client/views/admin/sidebarItems.ts b/apps/meteor/client/views/admin/sidebarItems.ts index a9dc4bf53514..d3f195069647 100644 --- a/apps/meteor/client/views/admin/sidebarItems.ts +++ b/apps/meteor/client/views/admin/sidebarItems.ts @@ -4,7 +4,8 @@ import { createSidebarItems } from '../../lib/createSidebarItems'; export const { registerSidebarItem: registerAdminSidebarItem, unregisterSidebarItem, - itemsSubscription, + getSidebarItems: getAdminSidebarItems, + subscribeToSidebarItems: subscribeToAdminSidebarItems, } = createSidebarItems([ { href: 'admin-info', diff --git a/apps/meteor/client/views/banners/BannerRegion.tsx b/apps/meteor/client/views/banners/BannerRegion.tsx index c87667e5f3de..a840540482b4 100644 --- a/apps/meteor/client/views/banners/BannerRegion.tsx +++ b/apps/meteor/client/views/banners/BannerRegion.tsx @@ -1,12 +1,12 @@ import React, { FC } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import * as banners from '../../lib/banners'; import LegacyBanner from './LegacyBanner'; import UiKitBanner from './UiKitBanner'; const BannerRegion: FC = () => { - const payload = useSubscription(banners.firstSubscription); + const payload = useSyncExternalStore(...banners.firstSubscription); if (!payload) { return null; diff --git a/apps/meteor/client/views/omnichannel/additionalForms.tsx b/apps/meteor/client/views/omnichannel/additionalForms.tsx index a8fab3c41a2c..800458471eba 100644 --- a/apps/meteor/client/views/omnichannel/additionalForms.tsx +++ b/apps/meteor/client/views/omnichannel/additionalForms.tsx @@ -1,28 +1,29 @@ -/* eslint-disable @typescript-eslint/no-empty-interface */ import { ReactElement } from 'react'; -import { Unsubscribe, useSubscription, Subscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; -// eslint-disable-next-line @typescript-eslint/interface-name-prefix +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/interface-name-prefix */ export interface EEFormHooks {} const createFormSubscription = (): { registerForm: (form: EEFormHooks) => void; unregisterForm: (form: keyof EEFormHooks) => void; - formsSubscription: Subscription; + formsSubscription: readonly [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => EEFormHooks]; getForm: (form: keyof EEFormHooks) => () => ReactElement; } => { let forms = {} as EEFormHooks; let updateCb = (): void => undefined; - const formsSubscription: Subscription = { - subscribe: (cb: () => void): Unsubscribe => { + const formsSubscription = [ + (cb: () => void): (() => void) => { updateCb = cb; return (): void => { updateCb = (): void => undefined; }; }, - getCurrentValue: (): EEFormHooks => forms, - }; + (): EEFormHooks => forms, + ] as const; + const registerForm = (newForm: EEFormHooks): void => { forms = { ...forms, ...newForm }; updateCb(); @@ -37,6 +38,8 @@ const createFormSubscription = (): { return { registerForm, unregisterForm, formsSubscription, getForm }; }; -export const { registerForm, unregisterForm, formsSubscription, getForm } = createFormSubscription(); +const { registerForm, unregisterForm, formsSubscription, getForm } = createFormSubscription(); + +export { registerForm, unregisterForm, getForm }; -export const useFormsSubscription = (): EEFormHooks => useSubscription(formsSubscription); +export const useFormsSubscription = (): EEFormHooks => useSyncExternalStore(...formsSubscription); diff --git a/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx b/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx index b9ed52e54b9b..cb3721baec75 100644 --- a/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx +++ b/apps/meteor/client/views/omnichannel/agents/AgentEdit.tsx @@ -3,13 +3,12 @@ import { Field, TextInput, Button, Margins, Box, MultiSelect, Icon, Select } fro import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useRoute, useSetting, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useRef, useState, FC, ReactElement } from 'react'; -import { useSubscription } from 'use-subscription'; import { getUserEmailAddress } from '../../../../lib/getUserEmailAddress'; import VerticalBar from '../../../components/VerticalBar'; import { useForm } from '../../../hooks/useForm'; import UserInfo from '../../room/contextualBar/UserInfo'; -import { formsSubscription } from '../additionalForms'; +import { useFormsSubscription } from '../additionalForms'; // TODO: TYPE: // Department @@ -46,7 +45,7 @@ const AgentEdit: FC = ({ data, userDepartments, availableDepartm () => (userDepartments.departments ? userDepartments.departments.map(({ departmentId }) => departmentId) : []), [userDepartments], ); - const eeForms = useSubscription(formsSubscription); + const eeForms = useFormsSubscription(); const saveRef = useRef({ values: {}, diff --git a/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursFormContainer.js b/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursFormContainer.js index 7eae87457fbc..c274ebee272d 100644 --- a/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursFormContainer.js +++ b/apps/meteor/client/views/omnichannel/businessHours/BusinessHoursFormContainer.js @@ -1,12 +1,11 @@ import { FieldGroup, Box } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import React, { useEffect, useState } from 'react'; -import { useSubscription } from 'use-subscription'; import { businessHourManager } from '../../../../app/livechat/client/views/app/business-hours/BusinessHours'; import { useForm } from '../../../hooks/useForm'; import { useReactiveValue } from '../../../hooks/useReactiveValue'; -import { formsSubscription } from '../additionalForms'; +import { useFormsSubscription } from '../additionalForms'; import BusinessHourForm from './BusinessHoursForm'; const useChangeHandler = (name, ref) => @@ -25,7 +24,7 @@ const getInitalData = ({ workHours }) => ({ const cleanFunc = () => {}; const BusinessHoursFormContainer = ({ data, saveRef, onChange = () => {} }) => { - const forms = useSubscription(formsSubscription); + const forms = useFormsSubscription(); const [hasChangesMultiple, setHasChangesMultiple] = useState(false); const [hasChangesTimeZone, setHasChangesTimeZone] = useState(false); diff --git a/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx b/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx index d7b92cb3463d..d5432f10eb23 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx @@ -3,13 +3,12 @@ import { useMutableCallback, useLocalStorage } from '@rocket.chat/fuselage-hooks import { useSetModal, useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import moment from 'moment'; import React, { Dispatch, FC, SetStateAction, useEffect, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; import AutoCompleteAgent from '../../../components/AutoCompleteAgent'; import AutoCompleteDepartment from '../../../components/AutoCompleteDepartment'; import GenericModal from '../../../components/GenericModal'; import { useEndpointData } from '../../../hooks/useEndpointData'; -import { formsSubscription } from '../additionalForms'; +import { useFormsSubscription } from '../additionalForms'; import Label from './Label'; import RemoveAllClosed from './RemoveAllClosed'; @@ -64,7 +63,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, ...props }) => { setCustomFields([]); }); - const forms = useSubscription(formsSubscription); + const forms = useFormsSubscription() as any; // TODO: Refactor the formsSubscription to use components instead of hooks (since the only thing the hook does is return a component) // Conditional hook was required since the whole formSubscription uses hooks in an incorrect manner diff --git a/apps/meteor/client/views/omnichannel/customFields/EditCustomFieldsPage.js b/apps/meteor/client/views/omnichannel/customFields/EditCustomFieldsPage.js index 852d31215bf0..89fdf1cff012 100644 --- a/apps/meteor/client/views/omnichannel/customFields/EditCustomFieldsPage.js +++ b/apps/meteor/client/views/omnichannel/customFields/EditCustomFieldsPage.js @@ -2,11 +2,10 @@ import { Box, Button, Icon, ButtonGroup, FieldGroup } from '@rocket.chat/fuselag import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useRoute, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState } from 'react'; -import { useSubscription } from 'use-subscription'; import Page from '../../../components/Page'; import { useForm } from '../../../hooks/useForm'; -import { formsSubscription } from '../additionalForms'; +import { useFormsSubscription } from '../additionalForms'; import CustomFieldsForm from './CustomFieldsForm'; const getInitialValues = (cf) => ({ @@ -24,7 +23,7 @@ const EditCustomFieldsPage = ({ customField, id, reload }) => { const [additionalValues, setAdditionalValues] = useState({}); - const { useCustomFieldsAdditionalForm = () => {} } = useSubscription(formsSubscription); + const { useCustomFieldsAdditionalForm = () => {} } = useFormsSubscription(); const AdditionalForm = useCustomFieldsAdditionalForm(); const router = useRoute('omnichannel-customfields'); diff --git a/apps/meteor/client/views/omnichannel/customFields/NewCustomFieldsPage.js b/apps/meteor/client/views/omnichannel/customFields/NewCustomFieldsPage.js index 65b02030cfec..b4d4dcd4a447 100644 --- a/apps/meteor/client/views/omnichannel/customFields/NewCustomFieldsPage.js +++ b/apps/meteor/client/views/omnichannel/customFields/NewCustomFieldsPage.js @@ -2,11 +2,10 @@ import { Box, Button, Icon, FieldGroup, ButtonGroup } from '@rocket.chat/fuselag import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useRoute, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState } from 'react'; -import { useSubscription } from 'use-subscription'; import Page from '../../../components/Page'; import { useForm } from '../../../hooks/useForm'; -import { formsSubscription } from '../additionalForms'; +import { useFormsSubscription } from '../additionalForms'; import CustomFieldsForm from './CustomFieldsForm'; const initialValues = { @@ -23,7 +22,7 @@ const NewCustomFieldsPage = ({ reload }) => { const [additionalValues, setAdditionalValues] = useState({}); - const { useCustomFieldsAdditionalForm = () => {} } = useSubscription(formsSubscription); + const { useCustomFieldsAdditionalForm = () => {} } = useFormsSubscription(); const AdditionalForm = useCustomFieldsAdditionalForm(); const router = useRoute('omnichannel-customfields'); diff --git a/apps/meteor/client/views/omnichannel/departments/EditDepartment.js b/apps/meteor/client/views/omnichannel/departments/EditDepartment.js index ccde330932d0..25a62e664262 100644 --- a/apps/meteor/client/views/omnichannel/departments/EditDepartment.js +++ b/apps/meteor/client/views/omnichannel/departments/EditDepartment.js @@ -15,7 +15,6 @@ import { import { useMutableCallback, useUniqueId } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useRoute, useMethod, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo, useState, useRef } from 'react'; -import { useSubscription } from 'use-subscription'; import { validateEmail } from '../../../../lib/emailValidator'; import Page from '../../../components/Page'; @@ -24,7 +23,7 @@ import { useRecordList } from '../../../hooks/lists/useRecordList'; import { useComponentDidUpdate } from '../../../hooks/useComponentDidUpdate'; import { useForm } from '../../../hooks/useForm'; import { AsyncStatePhase } from '../../../lib/asyncState'; -import { formsSubscription } from '../additionalForms'; +import { useFormsSubscription } from '../additionalForms'; import DepartmentsAgentsTable from './DepartmentsAgentsTable'; function withDefault(key, defaultValue) { @@ -42,7 +41,7 @@ function EditDepartment({ data, id, title, reload, allowedToForwardData }) { useDepartmentForwarding = () => {}, useDepartmentBusinessHours = () => {}, useSelectForwardDepartment = () => {}, - } = useSubscription(formsSubscription); + } = useFormsSubscription(); const initialAgents = useRef((data && data.agents) || []); diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js index 0f4a5578c4b3..fdcdeeaabc6a 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit.js @@ -2,7 +2,6 @@ import { Field, TextInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; import { hasAtLeastOnePermission } from '../../../../../../app/authorization/client'; import CustomFieldsForm from '../../../../../components/CustomFieldsForm'; @@ -11,7 +10,7 @@ import VerticalBar from '../../../../../components/VerticalBar'; import { AsyncStatePhase } from '../../../../../hooks/useAsyncState'; import { useEndpointData } from '../../../../../hooks/useEndpointData'; import { useForm } from '../../../../../hooks/useForm'; -import { formsSubscription } from '../../../additionalForms'; +import { useFormsSubscription } from '../../../additionalForms'; import { FormSkeleton } from '../../Skeleton'; const initialValuesRoom = { @@ -45,7 +44,7 @@ function RoomEdit({ room, visitor, reload, reloadInfo, close }) { const { handleTopic, handleTags, handlePriorityId } = handlersRoom; const { topic, tags, priorityId } = valuesRoom; - const forms = useSubscription(formsSubscription); + const forms = useFormsSubscription(); const { usePrioritiesSelect = () => {} } = forms; diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js index 6380f16ab565..fda1dc2e8dcc 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js +++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactNewEdit.js @@ -2,7 +2,6 @@ import { Field, TextInput, ButtonGroup, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; import { hasAtLeastOnePermission } from '../../../../../../app/authorization/client'; import { validateEmail } from '../../../../../../lib/emailValidator'; @@ -13,7 +12,7 @@ import { useComponentDidUpdate } from '../../../../../hooks/useComponentDidUpdat import { useEndpointData } from '../../../../../hooks/useEndpointData'; import { useForm } from '../../../../../hooks/useForm'; import { createToken } from '../../../../../lib/utils/createToken'; -import { formsSubscription } from '../../../additionalForms'; +import { useFormsSubscription } from '../../../additionalForms'; import { FormSkeleton } from '../../Skeleton'; const initialValues = { @@ -50,7 +49,7 @@ function ContactNewEdit({ id, data, close }) { const { values, handlers, hasUnsavedChanges: hasUnsavedChangesContact } = useForm(getInitialValues(data)); - const eeForms = useSubscription(formsSubscription); + const eeForms = useFormsSubscription(); const { useContactManager = () => {} } = eeForms; diff --git a/apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx b/apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx index ea646b6b7452..c7def16dd3be 100644 --- a/apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx +++ b/apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx @@ -1,16 +1,16 @@ import { useRoutePath, useCurrentRoute, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback, useEffect, FC, memo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { menu, SideNav } from '../../../../app/ui-utils/client'; import Sidebar from '../../../components/Sidebar'; import SidebarItemsAssemblerProps from '../../../components/Sidebar/SidebarItemsAssembler'; import { isLayoutEmbedded } from '../../../lib/utils/isLayoutEmbedded'; import SettingsProvider from '../../../providers/SettingsProvider'; -import { itemsSubscription } from '../sidebarItems'; +import { getOmnichannelSidebarItems, subscribeToOmnichannelSidebarItems } from '../sidebarItems'; const OmnichannelSidebar: FC = () => { - const items = useSubscription(itemsSubscription); + const items = useSyncExternalStore(subscribeToOmnichannelSidebarItems, getOmnichannelSidebarItems); const t = useTranslation(); const closeOmnichannelFlex = useCallback(() => { diff --git a/apps/meteor/client/views/omnichannel/sidebarItems.ts b/apps/meteor/client/views/omnichannel/sidebarItems.ts index 34e815a3effb..2cda8c2c9e33 100644 --- a/apps/meteor/client/views/omnichannel/sidebarItems.ts +++ b/apps/meteor/client/views/omnichannel/sidebarItems.ts @@ -4,7 +4,8 @@ import { createSidebarItems } from '../../lib/createSidebarItems'; export const { registerSidebarItem: registerOmnichannelSidebarItem, unregisterSidebarItem, - itemsSubscription, + getSidebarItems: getOmnichannelSidebarItems, + subscribeToSidebarItems: subscribeToOmnichannelSidebarItems, } = createSidebarItems([ { href: 'omnichannel/current', diff --git a/apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx b/apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx index c96c6a92075d..f9fda919d88c 100644 --- a/apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx +++ b/apps/meteor/client/views/room/MessageList/contexts/SelectedMessagesContext.tsx @@ -1,6 +1,5 @@ -import { OffCallbackHandler } from '@rocket.chat/emitter'; -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { createContext, useCallback, useContext } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { selectedMessageStore } from '../../providers/SelectedMessagesProvider'; @@ -14,28 +13,28 @@ export const SelectedMessageContext = createContext({ export const useIsSelectedMessage = (mid: string): boolean => { const { selectedMessageStore } = useContext(SelectedMessageContext); - const subscription = useMemo( - () => ({ - getCurrentValue: (): boolean => selectedMessageStore.isSelected(mid), - subscribe: (callback: () => void): OffCallbackHandler => selectedMessageStore.on(mid, callback), - }), - [mid, selectedMessageStore], + + const subscribe = useCallback( + (callback: () => void): (() => void) => selectedMessageStore.on(mid, callback), + [selectedMessageStore, mid], ); - return useSubscription(subscription); + + const getSnapshot = (): boolean => selectedMessageStore.isSelected(mid); + + return useSyncExternalStore(subscribe, getSnapshot); }; export const useIsSelecting = (): boolean => { const { selectedMessageStore } = useContext(SelectedMessageContext); - return useSubscription( - useMemo( - () => ({ - getCurrentValue: (): boolean => selectedMessageStore.getIsSelecting(), - subscribe: (callback: () => void): OffCallbackHandler => selectedMessageStore.on('toggleIsSelecting', callback), - }), - [selectedMessageStore], - ), + const subscribe = useCallback( + (callback: () => void): (() => void) => selectedMessageStore.on('toggleIsSelecting', callback), + [selectedMessageStore], ); + + const getSnapshot = (): boolean => selectedMessageStore.getIsSelecting(); + + return useSyncExternalStore(subscribe, getSnapshot); }; export const useToggleSelect = (mid: string): (() => void) => { @@ -48,13 +47,12 @@ export const useToggleSelect = (mid: string): (() => void) => { export const useCountSelected = (): number => { const { selectedMessageStore } = useContext(SelectedMessageContext); - return useSubscription( - useMemo( - () => ({ - getCurrentValue: (): number => selectedMessageStore.count(), - subscribe: (callback: () => void): OffCallbackHandler => selectedMessageStore.on('change', callback), - }), - [selectedMessageStore], - ), + const subscribe = useCallback( + (callback: () => void): (() => void) => selectedMessageStore.on('change', callback), + [selectedMessageStore], ); + + const getSnapshot = (): number => selectedMessageStore.count(); + + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/apps/meteor/client/views/room/MessageList/providers/MessageHighlightProvider.tsx b/apps/meteor/client/views/room/MessageList/providers/MessageHighlightProvider.tsx index 3d23ca9972ec..a9aec5439b62 100644 --- a/apps/meteor/client/views/room/MessageList/providers/MessageHighlightProvider.tsx +++ b/apps/meteor/client/views/room/MessageList/providers/MessageHighlightProvider.tsx @@ -1,11 +1,11 @@ import React, { ReactElement, ContextType, useMemo, ReactNode } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import MessageHighlightContext from '../contexts/MessageHighlightContext'; -import { messageHighlightSubscription } from './messageHighlightSubscription'; +import * as messageHighlightSubscription from './messageHighlightSubscription'; const MessageHighlightProvider = ({ children }: { children: ReactNode }): ReactElement => { - const highlightMessageId = useSubscription(messageHighlightSubscription); + const highlightMessageId = useSyncExternalStore(messageHighlightSubscription.subscribe, messageHighlightSubscription.getSnapshot); const contextValue = useMemo>( () => ({ diff --git a/apps/meteor/client/views/room/MessageList/providers/messageHighlightSubscription.ts b/apps/meteor/client/views/room/MessageList/providers/messageHighlightSubscription.ts index 31fcb3bab269..dff96e0b0105 100644 --- a/apps/meteor/client/views/room/MessageList/providers/messageHighlightSubscription.ts +++ b/apps/meteor/client/views/room/MessageList/providers/messageHighlightSubscription.ts @@ -1,31 +1,29 @@ import { IMessage } from '@rocket.chat/core-typings'; -import { Subscription, Unsubscribe } from 'use-subscription'; type SetHighlightFn = (_id: IMessage['_id']) => void; type ClearHighlightFn = (_id: IMessage['_id']) => void; type MessageHighlightSubscription = { - subscription: Subscription; + subscribe: (callback: () => void) => () => void; + getSnapshot: () => IMessage['_id'] | undefined; setHighlight: SetHighlightFn; clearHighlight: ClearHighlightFn; }; const createMessageHighlightSubscription = (): MessageHighlightSubscription => { - let updateCb: Unsubscribe = () => undefined; + let updateCb: () => void = () => undefined; let highlightMessageId: IMessage['_id'] | undefined; - const subscription: Subscription = { - subscribe: (cb) => { - updateCb = cb; - return (): void => { - updateCb = (): void => undefined; - }; - }, - - getCurrentValue: (): typeof highlightMessageId => highlightMessageId, + const subscribe = (cb: () => void): (() => void) => { + updateCb = cb; + return (): void => { + updateCb = (): void => undefined; + }; }; + const getSnapshot = (): typeof highlightMessageId => highlightMessageId; + const setHighlight = (_id: IMessage['_id']): void => { highlightMessageId = _id; updateCb(); @@ -36,11 +34,12 @@ const createMessageHighlightSubscription = (): MessageHighlightSubscription => { updateCb(); }; - return { subscription, setHighlight, clearHighlight }; + return { subscribe, getSnapshot, setHighlight, clearHighlight }; }; export const { - subscription: messageHighlightSubscription, + getSnapshot, + subscribe, setHighlight: setHighlightMessage, clearHighlight: clearHighlightMessage, } = createMessageHighlightSubscription(); diff --git a/apps/meteor/client/views/root/AppLayout.tsx b/apps/meteor/client/views/root/AppLayout.tsx index f883e61f1dac..137b542fcdb6 100644 --- a/apps/meteor/client/views/root/AppLayout.tsx +++ b/apps/meteor/client/views/root/AppLayout.tsx @@ -1,5 +1,5 @@ import React, { FC, Fragment, Suspense } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { appLayout } from '../../lib/appLayout'; import { blazePortals } from '../../lib/portals/blazePortals'; @@ -9,8 +9,8 @@ import { useTooltipHandling } from './useTooltipHandling'; const AppLayout: FC = () => { useTooltipHandling(); - const layout = useSubscription(appLayout); - const portals = useSubscription(blazePortals); + const layout = useSyncExternalStore(appLayout.subscribe, appLayout.getSnapshot); + const portals = useSyncExternalStore(blazePortals.subscribe, blazePortals.getSnapshot); return ( <> diff --git a/apps/meteor/client/views/root/PortalsWrapper.tsx b/apps/meteor/client/views/root/PortalsWrapper.tsx index 99c51a455a16..cc31f762e303 100644 --- a/apps/meteor/client/views/root/PortalsWrapper.tsx +++ b/apps/meteor/client/views/root/PortalsWrapper.tsx @@ -1,11 +1,11 @@ import React, { FC } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { portalsSubscription } from '../../lib/portals/portalsSubscription'; import PortalWrapper from './PortalWrapper'; const PortalsWrapper: FC = () => { - const portals = useSubscription(portalsSubscription); + const portals = useSyncExternalStore(portalsSubscription.subscribe, portalsSubscription.getSnapshot); return ( <> diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 2afada256b33..9d6599ad8be7 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -130,7 +130,7 @@ "@types/supertest": "^2.0.11", "@types/ua-parser-js": "^0.7.36", "@types/underscore.string": "0.0.38", - "@types/use-subscription": "^1.0.0", + "@types/use-sync-external-store": "^0.0.3", "@types/uuid": "^8.3.4", "@types/xml-crypto": "^1.4.1", "@types/xmldom": "^0.1.30", @@ -196,7 +196,7 @@ "@rocket.chat/forked-matrix-appservice-bridge": "^4.0.1", "@rocket.chat/forked-matrix-bot-sdk": "^0.6.0-beta.2", "@rocket.chat/fuselage": "0.32.0-dev.49", - "@rocket.chat/fuselage-hooks": "~0.31.12", + "@rocket.chat/fuselage-hooks": "~0.31.14-dev.7", "@rocket.chat/fuselage-polyfills": "~0.31.12", "@rocket.chat/fuselage-toastbar": "^0.32.0-dev.22", "@rocket.chat/fuselage-tokens": "~0.31.12", @@ -350,7 +350,7 @@ "underscore.string": "^3.3.6", "universal-perf-hooks": "^1.0.1", "url-polyfill": "^1.1.12", - "use-subscription": "~1.6.0", + "use-sync-external-store": "^1.2.0", "uuid": "^8.3.2", "vm2": "^3.9.9", "webdav": "^4.9.0", diff --git a/apps/meteor/tests/mocks/client/RouterContextMock.tsx b/apps/meteor/tests/mocks/client/RouterContextMock.tsx index 579d1e7367f6..ec99fa8eec97 100644 --- a/apps/meteor/tests/mocks/client/RouterContextMock.tsx +++ b/apps/meteor/tests/mocks/client/RouterContextMock.tsx @@ -1,5 +1,4 @@ import React, { ContextType, ReactElement, ReactNode, useMemo } from 'react'; -import { Subscription } from 'use-subscription'; import { RouterContext } from '@rocket.chat/ui-contexts'; type RouterContextMockProps = { @@ -11,28 +10,28 @@ type RouterContextMockProps = { const RouterContextMock = ({ children, pushRoute, replaceRoute }: RouterContextMockProps): ReactElement => { const value = useMemo>( () => ({ - queryRoutePath: (): Subscription => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryRouteUrl: (): Subscription => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), + queryRoutePath: (): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => undefined] => [ + () => (): void => undefined, + (): undefined => undefined, + ], + queryRouteUrl: (): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => undefined] => [ + () => (): void => undefined, + (): undefined => undefined, + ], pushRoute: pushRoute ?? ((): void => undefined), replaceRoute: replaceRoute ?? ((): void => undefined), - queryRouteParameter: (): Subscription => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryQueryStringParameter: (): Subscription => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryCurrentRoute: (): Subscription<[undefined, {}, {}, undefined]> => ({ - getCurrentValue: (): [undefined, {}, {}, undefined] => [undefined, {}, {}, undefined], - subscribe: () => (): void => undefined, - }), + queryRouteParameter: (): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => undefined] => [ + () => (): void => undefined, + (): undefined => undefined, + ], + queryQueryStringParameter: (): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => undefined] => [ + () => (): void => undefined, + (): undefined => undefined, + ], + queryCurrentRoute: (): [ + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => [undefined?, {}?, {}?, undefined?], + ] => [() => (): void => undefined, (): [undefined, {}, {}, undefined] => [undefined, {}, {}, undefined]], }), [pushRoute, replaceRoute], ); diff --git a/packages/ui-contexts/package.json b/packages/ui-contexts/package.json index eee275c870ec..4a4f7b70e619 100644 --- a/packages/ui-contexts/package.json +++ b/packages/ui-contexts/package.json @@ -5,18 +5,18 @@ "devDependencies": { "@rocket.chat/core-typings": "workspace:^", "@rocket.chat/emitter": "^0.31.11", - "@rocket.chat/fuselage-hooks": "^0.31.11", + "@rocket.chat/fuselage-hooks": "~0.31.14-dev.7", "@rocket.chat/rest-typings": "workspace:^", "@types/jest": "^27.4.1", "@types/mongodb": "~3.6.10", "@types/react": "^17", - "@types/use-subscription": "^1", + "@types/use-sync-external-store": "^0.0.3", "eslint": "^8.12.0", "jest": "^27.5.1", "react": "~17.0.2", "ts-jest": "^27.1.4", "typescript": "~4.3.5", - "use-subscription": "~1.6.0" + "use-sync-external-store": "^1.2.0" }, "peerDependencies": { "@rocket.chat/core-typings": "workspace:^", @@ -24,7 +24,7 @@ "@rocket.chat/fuselage-hooks": "*", "@rocket.chat/rest-typings": "workspace:^", "react": "~17.0.2", - "use-subscription": "*" + "use-sync-external-store": "^1.2.0" }, "scripts": { "lint": "eslint --ext .js,.jsx,.ts,.tsx .", diff --git a/packages/ui-contexts/src/AuthorizationContext.ts b/packages/ui-contexts/src/AuthorizationContext.ts index 5072d00ed56f..235838841646 100644 --- a/packages/ui-contexts/src/AuthorizationContext.ts +++ b/packages/ui-contexts/src/AuthorizationContext.ts @@ -1,7 +1,6 @@ import type { IRole } from '@rocket.chat/core-typings'; import type { IEmitter } from '@rocket.chat/emitter'; import { createContext } from 'react'; -import type { Subscription, Unsubscribe } from 'use-subscription'; import type { ObjectId } from 'mongodb'; export type IRoles = { [_id: string]: IRole }; @@ -13,30 +12,27 @@ export type RoleStore = IEmitter<{ }; export type AuthorizationContextValue = { - queryPermission(permission: string | ObjectId, scope?: string | ObjectId): Subscription; - queryAtLeastOnePermission(permission: (string | ObjectId)[], scope?: string | ObjectId): Subscription; - queryAllPermissions(permission: (string | ObjectId)[], scope?: string | ObjectId): Subscription; - queryRole(role: string | ObjectId): Subscription; + queryPermission( + permission: string | ObjectId, + scope?: string | ObjectId, + ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean]; + queryAtLeastOnePermission( + permission: (string | ObjectId)[], + scope?: string | ObjectId, + ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean]; + queryAllPermissions( + permission: (string | ObjectId)[], + scope?: string | ObjectId, + ): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean]; + queryRole(role: string | ObjectId): [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => boolean]; roleStore: RoleStore; }; export const AuthorizationContext = createContext({ - queryPermission: () => ({ - getCurrentValue: (): boolean => false, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryAtLeastOnePermission: () => ({ - getCurrentValue: (): boolean => false, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryAllPermissions: () => ({ - getCurrentValue: (): boolean => false, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryRole: () => ({ - getCurrentValue: (): boolean => false, - subscribe: (): Unsubscribe => (): void => undefined, - }), + queryPermission: () => [() => (): void => undefined, (): boolean => false], + queryAtLeastOnePermission: () => [() => (): void => undefined, (): boolean => false], + queryAllPermissions: () => [() => (): void => undefined, (): boolean => false], + queryRole: () => [() => (): void => undefined, (): boolean => false], roleStore: { roles: {}, emit: (): void => undefined, diff --git a/packages/ui-contexts/src/RouterContext.ts b/packages/ui-contexts/src/RouterContext.ts index 54fa4cd66d8e..6fd12b67e41c 100644 --- a/packages/ui-contexts/src/RouterContext.ts +++ b/packages/ui-contexts/src/RouterContext.ts @@ -1,5 +1,4 @@ import { createContext } from 'react'; -import type { Subscription } from 'use-subscription'; export type RouteName = string; @@ -14,44 +13,34 @@ export type RouterContextValue = { name: RouteName, parameters: RouteParameters | undefined, queryStringParameters: QueryStringParameters | undefined, - ) => Subscription; + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined]; queryRouteUrl: ( name: RouteName, parameters: RouteParameters | undefined, queryStringParameters: QueryStringParameters | undefined, - ) => Subscription; + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined]; pushRoute: (name: RouteName, parameters: RouteParameters | undefined, queryStringParameters: QueryStringParameters | undefined) => void; replaceRoute: ( name: RouteName, parameters: RouteParameters | undefined, queryStringParameters: QueryStringParameters | undefined, ) => void; - queryRouteParameter: (name: string) => Subscription; - queryQueryStringParameter: (name: string) => Subscription; - queryCurrentRoute: () => Subscription<[RouteName?, RouteParameters?, QueryStringParameters?, RouteGroupName?]>; + queryRouteParameter: (name: string) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined]; + queryQueryStringParameter: ( + name: string, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined]; + queryCurrentRoute: () => [ + subscribe: (onStoreChange: () => void) => () => void, + getSnapshot: () => [RouteName?, RouteParameters?, QueryStringParameters?, RouteGroupName?], + ]; }; export const RouterContext = createContext({ - queryRoutePath: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryRouteUrl: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), + queryRoutePath: () => [() => (): void => undefined, (): undefined => undefined], + queryRouteUrl: () => [() => (): void => undefined, (): undefined => undefined], pushRoute: () => undefined, replaceRoute: () => undefined, - queryRouteParameter: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryQueryStringParameter: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: () => (): void => undefined, - }), - queryCurrentRoute: () => ({ - getCurrentValue: (): [undefined, {}, {}, undefined] => [undefined, {}, {}, undefined], - subscribe: () => (): void => undefined, - }), + queryRouteParameter: () => [() => (): void => undefined, (): undefined => undefined], + queryQueryStringParameter: () => [() => (): void => undefined, (): undefined => undefined], + queryCurrentRoute: () => [() => (): void => undefined, (): [undefined, {}, {}, undefined] => [undefined, {}, {}, undefined]], }); diff --git a/packages/ui-contexts/src/SessionContext.ts b/packages/ui-contexts/src/SessionContext.ts index 250953bffaee..24cb77f5564a 100644 --- a/packages/ui-contexts/src/SessionContext.ts +++ b/packages/ui-contexts/src/SessionContext.ts @@ -1,15 +1,11 @@ import { createContext } from 'react'; -import type { Subscription, Unsubscribe } from 'use-subscription'; export type SessionContextValue = { - query: (name: string) => Subscription; + query: (name: string) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => unknown]; dispatch: (name: string, value: unknown) => void; }; export const SessionContext = createContext({ - query: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), + query: () => [() => (): void => undefined, (): undefined => undefined], dispatch: (): void => undefined, }); diff --git a/packages/ui-contexts/src/SettingsContext.ts b/packages/ui-contexts/src/SettingsContext.ts index ed27efa4f4e1..d9bb2f2e33d2 100644 --- a/packages/ui-contexts/src/SettingsContext.ts +++ b/packages/ui-contexts/src/SettingsContext.ts @@ -1,6 +1,5 @@ import type { SettingId, ISetting, GroupId, SectionName, TabId } from '@rocket.chat/core-typings'; import { createContext } from 'react'; -import type { Subscription, Unsubscribe } from 'use-subscription'; export type SettingsContextQuery = { readonly _id?: SettingId[]; @@ -12,21 +11,19 @@ export type SettingsContextQuery = { export type SettingsContextValue = { readonly hasPrivateAccess: boolean; readonly isLoading: boolean; - readonly querySetting: (_id: SettingId) => Subscription; - readonly querySettings: (query: SettingsContextQuery) => Subscription; + readonly querySetting: ( + _id: SettingId, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting | undefined]; + readonly querySettings: ( + query: SettingsContextQuery, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISetting[]]; readonly dispatch: (changes: Partial[]) => Promise; }; export const SettingsContext = createContext({ hasPrivateAccess: false, isLoading: false, - querySetting: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - querySettings: () => ({ - getCurrentValue: (): ISetting[] => [], - subscribe: (): Unsubscribe => (): void => undefined, - }), + querySetting: () => [(): (() => void) => (): void => undefined, (): undefined => undefined], + querySettings: () => [(): (() => void) => (): void => undefined, (): ISetting[] => []], dispatch: async () => undefined, }); diff --git a/packages/ui-contexts/src/UserContext.ts b/packages/ui-contexts/src/UserContext.ts index c44e835a1703..05071b86b723 100644 --- a/packages/ui-contexts/src/UserContext.ts +++ b/packages/ui-contexts/src/UserContext.ts @@ -1,7 +1,6 @@ import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; import type { ObjectId, FilterQuery } from 'mongodb'; import { createContext } from 'react'; -import type { Subscription, Unsubscribe } from 'use-subscription'; export type SubscriptionQuery = | { @@ -33,10 +32,24 @@ export type UserContextValue = { user: IUser | null; loginWithPassword: (user: string | object, password: string) => Promise; logout: () => Promise; - queryPreference: (key: string | ObjectId, defaultValue?: T) => Subscription; - querySubscription: (query: FilterQuery, fields?: Fields, sort?: Sort) => Subscription; - queryRoom: (query: FilterQuery, fields?: Fields, sort?: Sort) => Subscription; - querySubscriptions: (query: SubscriptionQuery, options?: FindOptions) => Subscription | []>; + queryPreference: ( + key: string | ObjectId, + defaultValue?: T, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => T | undefined]; + querySubscription: ( + query: FilterQuery, + fields?: Fields, + sort?: Sort, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => ISubscription | undefined]; + queryRoom: ( + query: FilterQuery, + fields?: Fields, + sort?: Sort, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => IRoom | undefined]; + querySubscriptions: ( + query: SubscriptionQuery, + options?: FindOptions, + ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => Array | []]; }; export const UserContext = createContext({ @@ -44,20 +57,8 @@ export const UserContext = createContext({ user: null, loginWithPassword: async () => undefined, logout: () => Promise.resolve(), - queryPreference: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - querySubscription: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - queryRoom: () => ({ - getCurrentValue: (): undefined => undefined, - subscribe: (): Unsubscribe => (): void => undefined, - }), - querySubscriptions: () => ({ - getCurrentValue: (): [] => [], - subscribe: (): Unsubscribe => (): void => undefined, - }), + queryPreference: () => [() => (): void => undefined, (): undefined => undefined], + querySubscription: () => [() => (): void => undefined, (): undefined => undefined], + queryRoom: () => [() => (): void => undefined, (): undefined => undefined], + querySubscriptions: () => [() => (): void => undefined, (): [] => []], }); diff --git a/packages/ui-contexts/src/hooks/useAllPermissions.ts b/packages/ui-contexts/src/hooks/useAllPermissions.ts index 5fe4a826282f..06acd07d92bb 100644 --- a/packages/ui-contexts/src/hooks/useAllPermissions.ts +++ b/packages/ui-contexts/src/hooks/useAllPermissions.ts @@ -1,11 +1,11 @@ import type { ObjectId } from 'mongodb'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { AuthorizationContext } from '../AuthorizationContext'; export const useAllPermissions = (permissions: (string | ObjectId)[], scope?: string | ObjectId): boolean => { const { queryAllPermissions } = useContext(AuthorizationContext); - const subscription = useMemo(() => queryAllPermissions(permissions, scope), [queryAllPermissions, permissions, scope]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryAllPermissions(permissions, scope), [queryAllPermissions, permissions, scope]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useAtLeastOnePermission.ts b/packages/ui-contexts/src/hooks/useAtLeastOnePermission.ts index 1bcb9d46a5a2..4a2fe9d060ab 100644 --- a/packages/ui-contexts/src/hooks/useAtLeastOnePermission.ts +++ b/packages/ui-contexts/src/hooks/useAtLeastOnePermission.ts @@ -1,11 +1,14 @@ import type { ObjectId } from 'mongodb'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { AuthorizationContext } from '../AuthorizationContext'; export const useAtLeastOnePermission = (permissions: (string | ObjectId)[], scope?: string | ObjectId): boolean => { const { queryAtLeastOnePermission } = useContext(AuthorizationContext); - const subscription = useMemo(() => queryAtLeastOnePermission(permissions, scope), [queryAtLeastOnePermission, permissions, scope]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo( + () => queryAtLeastOnePermission(permissions, scope), + [queryAtLeastOnePermission, permissions, scope], + ); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useCurrentRoute.ts b/packages/ui-contexts/src/hooks/useCurrentRoute.ts index 4a6ed6dbe63f..b7cc0e15f08e 100644 --- a/packages/ui-contexts/src/hooks/useCurrentRoute.ts +++ b/packages/ui-contexts/src/hooks/useCurrentRoute.ts @@ -1,10 +1,11 @@ import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { QueryStringParameters, RouteGroupName, RouteName, RouteParameters, RouterContext } from '../RouterContext'; export const useCurrentRoute = (): [RouteName?, RouteParameters?, QueryStringParameters?, RouteGroupName?] => { const { queryCurrentRoute } = useContext(RouterContext); - return useSubscription(useMemo(() => queryCurrentRoute(), [queryCurrentRoute])); + const [subscribe, getSnapshot] = useMemo(() => queryCurrentRoute(), [queryCurrentRoute]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/usePermission.ts b/packages/ui-contexts/src/hooks/usePermission.ts index 950b0832e7b5..cce1d6effd4a 100644 --- a/packages/ui-contexts/src/hooks/usePermission.ts +++ b/packages/ui-contexts/src/hooks/usePermission.ts @@ -1,11 +1,11 @@ import type { ObjectId } from 'mongodb'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { AuthorizationContext } from '../AuthorizationContext'; export const usePermission = (permission: string | ObjectId, scope?: string | ObjectId): boolean => { const { queryPermission } = useContext(AuthorizationContext); - const subscription = useMemo(() => queryPermission(permission, scope), [queryPermission, permission, scope]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryPermission(permission, scope), [queryPermission, permission, scope]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useQueryStringParameter.ts b/packages/ui-contexts/src/hooks/useQueryStringParameter.ts index d0dbc3d4ab3e..cc7a50dfad70 100644 --- a/packages/ui-contexts/src/hooks/useQueryStringParameter.ts +++ b/packages/ui-contexts/src/hooks/useQueryStringParameter.ts @@ -1,10 +1,12 @@ import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { RouterContext } from '../RouterContext'; export const useQueryStringParameter = (name: string): string | undefined => { const { queryQueryStringParameter } = useContext(RouterContext); - return useSubscription(useMemo(() => queryQueryStringParameter(name), [queryQueryStringParameter, name])); + const [subscribe, getSnapshot] = useMemo(() => queryQueryStringParameter(name), [queryQueryStringParameter, name]); + + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useRole.ts b/packages/ui-contexts/src/hooks/useRole.ts index b2cd3b23c1ee..da35e5b84059 100644 --- a/packages/ui-contexts/src/hooks/useRole.ts +++ b/packages/ui-contexts/src/hooks/useRole.ts @@ -1,11 +1,11 @@ import type { ObjectId } from 'mongodb'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { AuthorizationContext } from '../AuthorizationContext'; export const useRole = (role: string | ObjectId): boolean => { const { queryRole } = useContext(AuthorizationContext); - const subscription = useMemo(() => queryRole(role), [queryRole, role]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryRole(role), [queryRole, role]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useRolesDescription.ts b/packages/ui-contexts/src/hooks/useRolesDescription.ts index 4b523be76ce9..4cdc13983c77 100644 --- a/packages/ui-contexts/src/hooks/useRolesDescription.ts +++ b/packages/ui-contexts/src/hooks/useRolesDescription.ts @@ -1,25 +1,25 @@ import { useCallback, useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { AuthorizationContext, IRoles } from '../AuthorizationContext'; export const useRolesDescription = (): ((ids: Array) => [string]) => { const { roleStore } = useContext(AuthorizationContext); - const subscription = useMemo( - () => ({ - getCurrentValue: (): IRoles => roleStore.roles, - subscribe: (callback: () => void): (() => void) => { + const [subscribe, getSnapshot] = useMemo( + () => [ + (callback: () => void): (() => void) => { roleStore.on('change', callback); return (): void => { roleStore.off('change', callback); }; }, - }), + (): IRoles => roleStore.roles, + ], [roleStore], ); - const roles = useSubscription(subscription); + const roles = useSyncExternalStore(subscribe, getSnapshot); return useCallback((values) => values.map((role) => roles[role]?.description || roles[role]?.name || role), [roles]) as ( ids: Array, diff --git a/packages/ui-contexts/src/hooks/useRoute.ts b/packages/ui-contexts/src/hooks/useRoute.ts index db1db7c1519d..5b48d9225c14 100644 --- a/packages/ui-contexts/src/hooks/useRoute.ts +++ b/packages/ui-contexts/src/hooks/useRoute.ts @@ -14,10 +14,9 @@ export const useRoute = (name: string): Route => { return useMemo( () => ({ - getPath: (parameters, queryStringParameters): string | undefined => - queryRoutePath(name, parameters, queryStringParameters).getCurrentValue(), + getPath: (parameters, queryStringParameters): string | undefined => queryRoutePath(name, parameters, queryStringParameters)[1](), getUrl: (parameters, queryStringParameters): ReturnType => - queryRouteUrl(name, parameters, queryStringParameters).getCurrentValue(), + queryRouteUrl(name, parameters, queryStringParameters)[1](), push: (parameters, queryStringParameters): ReturnType => pushRoute(name, parameters, queryStringParameters), replace: (parameters, queryStringParameters): ReturnType => replaceRoute(name, parameters, queryStringParameters), }), diff --git a/packages/ui-contexts/src/hooks/useRouteParameter.ts b/packages/ui-contexts/src/hooks/useRouteParameter.ts index 01806bbc5236..eaae7e45fd06 100644 --- a/packages/ui-contexts/src/hooks/useRouteParameter.ts +++ b/packages/ui-contexts/src/hooks/useRouteParameter.ts @@ -1,10 +1,12 @@ import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { RouterContext } from '../RouterContext'; export const useRouteParameter = (name: string): string | undefined => { const { queryRouteParameter } = useContext(RouterContext); - return useSubscription(useMemo(() => queryRouteParameter(name), [queryRouteParameter, name])); + const [subscribe, getSnapshot] = useMemo(() => queryRouteParameter(name), [queryRouteParameter, name]); + + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useRoutePath.ts b/packages/ui-contexts/src/hooks/useRoutePath.ts index a30e1ec63f1a..f159b1fa8b5a 100644 --- a/packages/ui-contexts/src/hooks/useRoutePath.ts +++ b/packages/ui-contexts/src/hooks/useRoutePath.ts @@ -1,5 +1,5 @@ import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { QueryStringParameters, RouteParameters, RouterContext } from '../RouterContext'; @@ -10,7 +10,10 @@ export const useRoutePath = ( ): string | undefined => { const { queryRoutePath } = useContext(RouterContext); - return useSubscription( - useMemo(() => queryRoutePath(name, parameters, queryStringParameters), [queryRoutePath, name, parameters, queryStringParameters]), + const [subscribe, getSnapshot] = useMemo( + () => queryRoutePath(name, parameters, queryStringParameters), + [queryRoutePath, name, parameters, queryStringParameters], ); + + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useRouteUrl.ts b/packages/ui-contexts/src/hooks/useRouteUrl.ts index b88adcd44887..9d1f8908710e 100644 --- a/packages/ui-contexts/src/hooks/useRouteUrl.ts +++ b/packages/ui-contexts/src/hooks/useRouteUrl.ts @@ -1,5 +1,5 @@ import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { QueryStringParameters, RouteParameters, RouterContext } from '../RouterContext'; @@ -10,7 +10,10 @@ export const useRouteUrl = ( ): string | undefined => { const { queryRouteUrl } = useContext(RouterContext); - return useSubscription( - useMemo(() => queryRouteUrl(name, parameters, queryStringParameters), [queryRouteUrl, name, parameters, queryStringParameters]), + const [subscribe, getSnapshot] = useMemo( + () => queryRouteUrl(name, parameters, queryStringParameters), + [queryRouteUrl, name, parameters, queryStringParameters], ); + + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useSession.ts b/packages/ui-contexts/src/hooks/useSession.ts index f08606917278..0dd1826de15d 100644 --- a/packages/ui-contexts/src/hooks/useSession.ts +++ b/packages/ui-contexts/src/hooks/useSession.ts @@ -1,10 +1,10 @@ import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { SessionContext } from '../SessionContext'; export const useSession = (name: string): unknown => { const { query } = useContext(SessionContext); - const subscription = useMemo(() => query(name), [query, name]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => query(name), [query, name]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useSettingStructure.ts b/packages/ui-contexts/src/hooks/useSettingStructure.ts index 8139e77ce5fa..19eeb30815f5 100644 --- a/packages/ui-contexts/src/hooks/useSettingStructure.ts +++ b/packages/ui-contexts/src/hooks/useSettingStructure.ts @@ -1,11 +1,11 @@ import type { SettingId, ISetting } from '@rocket.chat/core-typings'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { SettingsContext } from '../SettingsContext'; export const useSettingStructure = (_id: SettingId): ISetting | undefined => { const { querySetting } = useContext(SettingsContext); - const subscription = useMemo(() => querySetting(_id), [querySetting, _id]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => querySetting(_id), [querySetting, _id]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useSettings.ts b/packages/ui-contexts/src/hooks/useSettings.ts index f561034d99fd..e2839a2ae566 100644 --- a/packages/ui-contexts/src/hooks/useSettings.ts +++ b/packages/ui-contexts/src/hooks/useSettings.ts @@ -1,11 +1,11 @@ import type { ISetting } from '@rocket.chat/core-typings'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { SettingsContext, SettingsContextQuery } from '../SettingsContext'; export const useSettings = (query?: SettingsContextQuery): ISetting[] => { const { querySettings } = useContext(SettingsContext); - const subscription = useMemo(() => querySettings(query ?? {}), [querySettings, query]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => querySettings(query ?? {}), [querySettings, query]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useUserPreference.ts b/packages/ui-contexts/src/hooks/useUserPreference.ts index cb6a715927e2..2865cade101a 100644 --- a/packages/ui-contexts/src/hooks/useUserPreference.ts +++ b/packages/ui-contexts/src/hooks/useUserPreference.ts @@ -1,10 +1,10 @@ import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { UserContext } from '../UserContext'; export const useUserPreference = (key: string, defaultValue?: T): T | undefined => { const { queryPreference } = useContext(UserContext); - const subscription = useMemo(() => queryPreference(key, defaultValue), [queryPreference, key, defaultValue]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryPreference(key, defaultValue), [queryPreference, key, defaultValue]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useUserRoom.ts b/packages/ui-contexts/src/hooks/useUserRoom.ts index 412e7165fcf4..f67a63eb07d0 100644 --- a/packages/ui-contexts/src/hooks/useUserRoom.ts +++ b/packages/ui-contexts/src/hooks/useUserRoom.ts @@ -1,11 +1,11 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { Fields, UserContext } from '../UserContext'; export const useUserRoom = (rid: string, fields?: Fields): IRoom | undefined => { const { queryRoom } = useContext(UserContext); - const subscription = useMemo(() => queryRoom({ _id: rid }, fields), [queryRoom, rid, fields]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => queryRoom({ _id: rid }, fields), [queryRoom, rid, fields]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useUserSubscription.ts b/packages/ui-contexts/src/hooks/useUserSubscription.ts index 112c016253ec..c5643c7432c9 100644 --- a/packages/ui-contexts/src/hooks/useUserSubscription.ts +++ b/packages/ui-contexts/src/hooks/useUserSubscription.ts @@ -1,11 +1,11 @@ import type { ISubscription } from '@rocket.chat/core-typings'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { Fields, UserContext } from '../UserContext'; export const useUserSubscription = (rid: string, fields?: Fields): ISubscription | undefined => { const { querySubscription } = useContext(UserContext); - const subscription = useMemo(() => querySubscription({ rid }, fields), [querySubscription, rid, fields]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => querySubscription({ rid }, fields), [querySubscription, rid, fields]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useUserSubscriptionByName.ts b/packages/ui-contexts/src/hooks/useUserSubscriptionByName.ts index 716ae5fb1108..5768113fc2fb 100644 --- a/packages/ui-contexts/src/hooks/useUserSubscriptionByName.ts +++ b/packages/ui-contexts/src/hooks/useUserSubscriptionByName.ts @@ -1,11 +1,11 @@ import type { ISubscription } from '@rocket.chat/core-typings'; import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { Fields, Sort, UserContext } from '../UserContext'; export const useUserSubscriptionByName = (name: string, fields?: Fields, sort?: Sort): ISubscription | undefined => { const { querySubscription } = useContext(UserContext); - const subscription = useMemo(() => querySubscription({ name }, fields, sort), [querySubscription, name, fields, sort]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => querySubscription({ name }, fields, sort), [querySubscription, name, fields, sort]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useUserSubscriptions.ts b/packages/ui-contexts/src/hooks/useUserSubscriptions.ts index f73d42eca35f..ffcb0b5cb7f5 100644 --- a/packages/ui-contexts/src/hooks/useUserSubscriptions.ts +++ b/packages/ui-contexts/src/hooks/useUserSubscriptions.ts @@ -1,11 +1,11 @@ import { useContext, useMemo } from 'react'; -import { useSubscription } from 'use-subscription'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import type { ISubscription } from '../../../core-typings/dist'; import { FindOptions, SubscriptionQuery, UserContext } from '../UserContext'; export const useUserSubscriptions = (query: SubscriptionQuery, options?: FindOptions): Array | [] => { const { querySubscriptions } = useContext(UserContext); - const subscription = useMemo(() => querySubscriptions(query, options), [querySubscriptions, query, options]); - return useSubscription(subscription); + const [subscribe, getSnapshot] = useMemo(() => querySubscriptions(query, options), [querySubscriptions, query, options]); + return useSyncExternalStore(subscribe, getSnapshot); }; diff --git a/yarn.lock b/yarn.lock index 06f4fc2cfcfa..1701f83058a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3503,7 +3503,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-hooks@npm:^0.31.11, @rocket.chat/fuselage-hooks@npm:^0.31.13, @rocket.chat/fuselage-hooks@npm:~0.31.12": +"@rocket.chat/fuselage-hooks@npm:^0.31.13": version: 0.31.13 resolution: "@rocket.chat/fuselage-hooks@npm:0.31.13" dependencies: @@ -3529,6 +3529,18 @@ __metadata: languageName: node linkType: hard +"@rocket.chat/fuselage-hooks@npm:~0.31.14-dev.7": + version: 0.31.14-dev.7 + resolution: "@rocket.chat/fuselage-hooks@npm:0.31.14-dev.7" + dependencies: + use-sync-external-store: ~1.2.0 + peerDependencies: + "@rocket.chat/fuselage-tokens": "*" + react: ^17.0.2 + checksum: ad2a1a61189d694e5e70ab73dfc20a7bea7ad0686be97993b24c6053b5cfa027e6b70690b75b5e9c1e8712a2b41f7d031a416c5e6781e9c4b31b5c0a40529ea0 + languageName: node + linkType: hard + "@rocket.chat/fuselage-polyfills@npm:~0.31.12": version: 0.31.13 resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.13" @@ -3807,7 +3819,7 @@ __metadata: "@rocket.chat/forked-matrix-appservice-bridge": ^4.0.1 "@rocket.chat/forked-matrix-bot-sdk": ^0.6.0-beta.2 "@rocket.chat/fuselage": 0.32.0-dev.49 - "@rocket.chat/fuselage-hooks": ~0.31.12 + "@rocket.chat/fuselage-hooks": ~0.31.14-dev.7 "@rocket.chat/fuselage-polyfills": ~0.31.12 "@rocket.chat/fuselage-toastbar": ^0.32.0-dev.22 "@rocket.chat/fuselage-tokens": ~0.31.12 @@ -3894,7 +3906,7 @@ __metadata: "@types/supertest": ^2.0.11 "@types/ua-parser-js": ^0.7.36 "@types/underscore.string": 0.0.38 - "@types/use-subscription": ^1.0.0 + "@types/use-sync-external-store": ^0.0.3 "@types/uuid": ^8.3.4 "@types/xml-crypto": ^1.4.1 "@types/xmldom": ^0.1.30 @@ -4071,7 +4083,7 @@ __metadata: underscore.string: ^3.3.6 universal-perf-hooks: ^1.0.1 url-polyfill: ^1.1.12 - use-subscription: ~1.6.0 + use-sync-external-store: ^1.2.0 uuid: ^8.3.2 vm2: ^3.9.9 webdav: ^4.9.0 @@ -4227,25 +4239,25 @@ __metadata: dependencies: "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/emitter": ^0.31.11 - "@rocket.chat/fuselage-hooks": ^0.31.11 + "@rocket.chat/fuselage-hooks": ~0.31.14-dev.7 "@rocket.chat/rest-typings": "workspace:^" "@types/jest": ^27.4.1 "@types/mongodb": ~3.6.10 "@types/react": ^17 - "@types/use-subscription": ^1 + "@types/use-sync-external-store": ^0.0.3 eslint: ^8.12.0 jest: ^27.5.1 react: ~17.0.2 ts-jest: ^27.1.4 typescript: ~4.3.5 - use-subscription: ~1.6.0 + use-sync-external-store: ^1.2.0 peerDependencies: "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/emitter": "*" "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/rest-typings": "workspace:^" react: ~17.0.2 - use-subscription: "*" + use-sync-external-store: ^1.2.0 languageName: unknown linkType: soft @@ -6832,10 +6844,10 @@ __metadata: languageName: node linkType: hard -"@types/use-subscription@npm:^1, @types/use-subscription@npm:^1.0.0": - version: 1.0.0 - resolution: "@types/use-subscription@npm:1.0.0" - checksum: 47fff868682692ecda7110bd04ba4c5b1324854c0bcccc765606a42d4bd9be475207413c8829a883b98e7edd801100df53876da0ff89ac21a8f964e440636ef2 +"@types/use-sync-external-store@npm:^0.0.3": + version: 0.0.3 + resolution: "@types/use-sync-external-store@npm:0.0.3" + checksum: 161ddb8eec5dbe7279ac971531217e9af6b99f7783213566d2b502e2e2378ea19cf5e5ea4595039d730aa79d3d35c6567d48599f69773a02ffcff1776ec2a44e languageName: node linkType: hard @@ -10061,9 +10073,9 @@ __metadata: linkType: hard "caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001349": - version: 1.0.30001352 - resolution: "caniuse-lite@npm:1.0.30001352" - checksum: 575ad031349e56224471859decd100d0f90c804325bf1b543789b212d6126f6e18925766b325b1d96f75e48df0036e68f92af26d1fb175803fd6ad935bc807ac + version: 1.0.30001356 + resolution: "caniuse-lite@npm:1.0.30001356" + checksum: 2b1f377f503de8c3d585b85b648422d1a202528d04321aaff5e37054cf413e927db1cc37a3408b44646c6eab7b9e38cae4b44b3a81fbf2e2f8f676cf559518b5 languageName: node linkType: hard @@ -31203,12 +31215,12 @@ __metadata: languageName: node linkType: hard -"use-subscription@npm:~1.6.0": - version: 1.6.0 - resolution: "use-subscription@npm:1.6.0" +"use-sync-external-store@npm:^1.2.0, use-sync-external-store@npm:~1.2.0": + version: 1.2.0 + resolution: "use-sync-external-store@npm:1.2.0" peerDependencies: - react: ^18.0.0 - checksum: 0ce15a3ed1f66f78cb2d7a83ff4980e2ac93054b0bcbdc4e492e77fed599233372aaaf9c9fd9b370484c491c5aee0b980859db1108ad63ee4cdba0f468c4f5d1 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a languageName: node linkType: hard