From 3698c683db35ebec4e26f17fbd7a8d045a1082c5 Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Tue, 15 Jul 2025 11:32:16 +0200 Subject: [PATCH] Refactor dialog old component states (#13186) This PR refactors dialog old component state management. --- .../app/components/AppRouterProviders.tsx | 2 +- .../__stories__/MatchColumns.stories.tsx | 2 +- .../__stories__/SelectHeader.stories.tsx | 2 +- .../__stories__/SelectSheet.stories.tsx | 2 +- .../components/__stories__/Upload.stories.tsx | 2 +- .../__stories__/Validation.stories.tsx | 2 +- .../components/DialogManager.tsx | 7 ++- .../components/DialogManagerEffect.tsx | 8 ++- .../DialogComponentInstanceContext.ts | 3 + .../hooks/__tests__/useDialogManager.test.tsx | 24 +++----- .../useDialogManagerScopedStates.test.tsx | 47 -------------- .../internal/useDialogManagerScopedStates.ts | 25 -------- .../dialog-manager/hooks/useDialogManager.ts | 61 ++++++++++--------- .../scopes/DialogManagerScope.tsx | 12 ++-- .../DialogManagerScopeInternalContext.ts | 7 --- .../states/dialogInternalComponentState.ts | 19 ++++++ .../states/dialogInternalScopedState.ts | 16 ----- 17 files changed, 88 insertions(+), 153 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/feedback/dialog-manager/contexts/DialogComponentInstanceContext.ts delete mode 100644 packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/internal/__tests__/useDialogManagerScopedStates.test.tsx delete mode 100644 packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/internal/useDialogManagerScopedStates.ts delete mode 100644 packages/twenty-front/src/modules/ui/feedback/dialog-manager/scopes/scope-internal-context/DialogManagerScopeInternalContext.ts create mode 100644 packages/twenty-front/src/modules/ui/feedback/dialog-manager/states/dialogInternalComponentState.ts delete mode 100644 packages/twenty-front/src/modules/ui/feedback/dialog-manager/states/dialogInternalScopedState.ts diff --git a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx index 35d6abc16..c7d7015b4 100644 --- a/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx +++ b/packages/twenty-front/src/modules/app/components/AppRouterProviders.tsx @@ -51,7 +51,7 @@ export const AppRouterProviders = () => { - + diff --git a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/__stories__/MatchColumns.stories.tsx b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/__stories__/MatchColumns.stories.tsx index 89fc964a8..cd1d1811e 100644 --- a/packages/twenty-front/src/modules/spreadsheet-import/steps/components/__stories__/MatchColumns.stories.tsx +++ b/packages/twenty-front/src/modules/spreadsheet-import/steps/components/__stories__/MatchColumns.stories.tsx @@ -62,7 +62,7 @@ const mockData = [ ]; export const Default = () => ( - + = { export default meta; export const Default = () => ( - + ( - + = { export default meta; export const Default = () => ( - + null}> ( - + { - const { dialogInternal } = useDialogManagerScopedStates(); + const dialogInternal = useRecoilComponentValueV2( + dialogInternalComponentState, + ); const { closeDialog } = useDialogManager(); return ( diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/DialogManagerEffect.tsx b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/DialogManagerEffect.tsx index e641dd93d..6dc8dd9e0 100644 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/DialogManagerEffect.tsx +++ b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/components/DialogManagerEffect.tsx @@ -3,10 +3,14 @@ import { useEffect } from 'react'; import { DIALOG_FOCUS_ID } from '@/ui/feedback/dialog-manager/constants/DialogFocusId'; import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; -import { useDialogManagerScopedStates } from '../hooks/internal/useDialogManagerScopedStates'; + +import { dialogInternalComponentState } from '@/ui/feedback/dialog-manager/states/dialogInternalComponentState'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; export const DialogManagerEffect = () => { - const { dialogInternal } = useDialogManagerScopedStates(); + const dialogInternal = useRecoilComponentValueV2( + dialogInternalComponentState, + ); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/contexts/DialogComponentInstanceContext.ts b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/contexts/DialogComponentInstanceContext.ts new file mode 100644 index 000000000..a87732357 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/contexts/DialogComponentInstanceContext.ts @@ -0,0 +1,3 @@ +import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext'; + +export const DialogComponentInstanceContext = createComponentInstanceContext(); diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/__tests__/useDialogManager.test.tsx b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/__tests__/useDialogManager.test.tsx index a5d40c996..ba83b25a8 100644 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/__tests__/useDialogManager.test.tsx +++ b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/__tests__/useDialogManager.test.tsx @@ -2,10 +2,11 @@ import { act, renderHook } from '@testing-library/react'; import { RecoilRoot } from 'recoil'; import { v4 as uuidv4 } from 'uuid'; -import { useDialogManagerScopedStates } from '@/ui/feedback/dialog-manager/hooks/internal/useDialogManagerScopedStates'; import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager'; import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope'; +import { dialogInternalComponentState } from '@/ui/feedback/dialog-manager/states/dialogInternalComponentState'; import { DialogOptions } from '@/ui/feedback/dialog-manager/types/DialogOptions'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; const mockedUuid = 'mocked-uuid'; jest.mock('uuid'); @@ -15,7 +16,7 @@ jest.mock('uuid'); const dialogManagerScopeId = 'dialog-manager'; const Wrapper = ({ children }: { children: React.ReactNode }) => ( - + {children} @@ -74,12 +75,8 @@ const dialogOptionsArray: DialogOptionsArray = [ const renderHooks = () => { const { result } = renderHook( () => ({ - dialogManager: useDialogManager({ - dialogManagerScopeId: dialogManagerScopeId, - }), - internalState: useDialogManagerScopedStates({ - dialogManagerScopeId, - }), + dialogManager: useDialogManager(), + dialogInternal: useRecoilComponentValueV2(dialogInternalComponentState), }), renderHookConfig, ); @@ -107,7 +104,7 @@ describe('useDialogManager', () => { result.current.dialogManager.enqueueDialog(dialogOptionsArray[0]); }); - const { dialogInternal } = result.current.internalState; + const { dialogInternal } = result.current; expect(dialogInternal.maxQueue).toEqual(2); expect(dialogInternal.queue).toHaveLength(1); @@ -127,7 +124,7 @@ describe('useDialogManager', () => { result.current.dialogManager.enqueueDialog(dialogOptionsArray[1]); }); - const { dialogInternal } = result.current.internalState; + const { dialogInternal } = result.current; expect(dialogInternal.maxQueue).toEqual(2); expect(dialogInternal.queue).toHaveLength(2); @@ -148,7 +145,7 @@ describe('useDialogManager', () => { result.current.dialogManager.enqueueDialog(dialogOptionsArray[2]); }); - const { dialogInternal } = result.current.internalState; + const { dialogInternal } = result.current; expect(dialogInternal.maxQueue).toEqual(2); expect(dialogInternal.queue).toHaveLength(2); @@ -170,8 +167,7 @@ describe('useDialogManager', () => { dialogOptionsArray[1], ]); - const { dialogInternal: stateAfterEnqueue } = - result.current.internalState; + const { dialogInternal: stateAfterEnqueue } = result.current; expect(stateAfterEnqueue.maxQueue).toEqual(2); expect(stateAfterEnqueue.queue).toHaveLength(2); @@ -186,7 +182,7 @@ describe('useDialogManager', () => { queue: [], }; - const { dialogInternal: stateAfterClose } = result.current.internalState; + const { dialogInternal: stateAfterClose } = result.current; expect(stateAfterClose).toEqual(expectReturnWhenClose); expect(stateAfterClose.maxQueue).toEqual(2); diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/internal/__tests__/useDialogManagerScopedStates.test.tsx b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/internal/__tests__/useDialogManagerScopedStates.test.tsx deleted file mode 100644 index 8f2d87824..000000000 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/internal/__tests__/useDialogManagerScopedStates.test.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { act, renderHook } from '@testing-library/react'; -import { RecoilRoot } from 'recoil'; - -import { useDialogManagerScopedStates } from '@/ui/feedback/dialog-manager/hooks/internal/useDialogManagerScopedStates'; -import { DialogManagerScope } from '@/ui/feedback/dialog-manager/scopes/DialogManagerScope'; - -const dialogManagerScopeId = 'dialog-manager'; - -const defaultReturnDialogState = { maxQueue: 2, queue: [] }; - -const updatedReturnDialogState = { - maxQueue: 5, - queue: [{ id: 'fakeId', title: 'testTitle', message: 'testMessage' }], -}; - -const Wrapper = ({ children }: { children: React.ReactNode }) => { - return ( - - - {children} - - - ); -}; - -describe('useDialogManagerScopedStates', () => { - it('Should return a dialog state and a function to update the state', async () => { - const { result } = renderHook( - () => - useDialogManagerScopedStates({ - dialogManagerScopeId, - }), - { - wrapper: Wrapper, - }, - ); - - expect(result.current.dialogInternal).toEqual(defaultReturnDialogState); - expect(result.current.setDialogInternal).toBeInstanceOf(Function); - - await act(async () => { - result.current.setDialogInternal(updatedReturnDialogState); - }); - - expect(result.current.dialogInternal).toEqual(updatedReturnDialogState); - }); -}); diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/internal/useDialogManagerScopedStates.ts b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/internal/useDialogManagerScopedStates.ts deleted file mode 100644 index f032f25b4..000000000 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/internal/useDialogManagerScopedStates.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useRecoilScopedStateV2 } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedStateV2'; -import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; - -import { DialogManagerScopeInternalContext } from '../../scopes/scope-internal-context/DialogManagerScopeInternalContext'; -import { dialogInternalScopedState } from '../../states/dialogInternalScopedState'; - -type useDialogManagerScopedStatesProps = { - dialogManagerScopeId?: string; -}; - -export const useDialogManagerScopedStates = ( - props?: useDialogManagerScopedStatesProps, -) => { - const scopeId = useAvailableScopeIdOrThrow( - DialogManagerScopeInternalContext, - props?.dialogManagerScopeId, - ); - - const [dialogInternal, setDialogInternal] = useRecoilScopedStateV2( - dialogInternalScopedState, - scopeId, - ); - - return { dialogInternal, setDialogInternal }; -}; diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/useDialogManager.ts b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/useDialogManager.ts index 0c248ad84..66f62b497 100644 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/useDialogManager.ts +++ b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/hooks/useDialogManager.ts @@ -1,22 +1,17 @@ import { useRecoilCallback } from 'recoil'; import { v4 } from 'uuid'; -import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; - import { DIALOG_FOCUS_ID } from '@/ui/feedback/dialog-manager/constants/DialogFocusId'; import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; -import { DialogManagerScopeInternalContext } from '../scopes/scope-internal-context/DialogManagerScopeInternalContext'; -import { dialogInternalScopedState } from '../states/dialogInternalScopedState'; + +import { DialogComponentInstanceContext } from '@/ui/feedback/dialog-manager/contexts/DialogComponentInstanceContext'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; +import { dialogInternalComponentState } from '../states/dialogInternalComponentState'; import { DialogOptions } from '../types/DialogOptions'; -type useDialogManagerProps = { - dialogManagerScopeId?: string; -}; - -export const useDialogManager = (props?: useDialogManagerProps) => { - const scopeId = useAvailableScopeIdOrThrow( - DialogManagerScopeInternalContext, - props?.dialogManagerScopeId, +export const useDialogManager = () => { + const componentInstanceId = useAvailableComponentInstanceIdOrThrow( + DialogComponentInstanceContext, ); const { removeFocusItemFromFocusStackById } = @@ -25,33 +20,43 @@ export const useDialogManager = (props?: useDialogManagerProps) => { const closeDialog = useRecoilCallback( ({ set }) => (id: string) => { - set(dialogInternalScopedState({ scopeId: scopeId }), (prevState) => ({ - ...prevState, - queue: prevState.queue.filter((dialog) => dialog.id !== id), - })); + set( + dialogInternalComponentState.atomFamily({ + instanceId: componentInstanceId, + }), + (prevState) => ({ + ...prevState, + queue: prevState.queue.filter((dialog) => dialog.id !== id), + }), + ); removeFocusItemFromFocusStackById({ focusId: DIALOG_FOCUS_ID }); }, - [removeFocusItemFromFocusStackById, scopeId], + [componentInstanceId, removeFocusItemFromFocusStackById], ); const setDialogQueue = useRecoilCallback( ({ set }) => (newValue) => - set(dialogInternalScopedState({ scopeId: scopeId }), (prev) => { - if (prev.queue.length >= prev.maxQueue) { + set( + dialogInternalComponentState.atomFamily({ + instanceId: componentInstanceId, + }), + (prev) => { + if (prev.queue.length >= prev.maxQueue) { + return { + ...prev, + queue: [...prev.queue.slice(1), newValue] as DialogOptions[], + }; + } + return { ...prev, - queue: [...prev.queue.slice(1), newValue] as DialogOptions[], + queue: [...prev.queue, newValue] as DialogOptions[], }; - } - - return { - ...prev, - queue: [...prev.queue, newValue] as DialogOptions[], - }; - }), - [scopeId], + }, + ), + [componentInstanceId], ); const enqueueDialog = (options?: Omit) => { diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/scopes/DialogManagerScope.tsx b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/scopes/DialogManagerScope.tsx index 6d863c4e8..82efa8440 100644 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/scopes/DialogManagerScope.tsx +++ b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/scopes/DialogManagerScope.tsx @@ -1,21 +1,21 @@ import { ReactNode } from 'react'; -import { DialogManagerScopeInternalContext } from './scope-internal-context/DialogManagerScopeInternalContext'; +import { DialogComponentInstanceContext } from '@/ui/feedback/dialog-manager/contexts/DialogComponentInstanceContext'; type DialogManagerScopeProps = { children: ReactNode; - dialogManagerScopeId: string; + dialogComponentInstanceId: string; }; export const DialogManagerScope = ({ children, - dialogManagerScopeId, + dialogComponentInstanceId, }: DialogManagerScopeProps) => { return ( - {children} - + ); }; diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/scopes/scope-internal-context/DialogManagerScopeInternalContext.ts b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/scopes/scope-internal-context/DialogManagerScopeInternalContext.ts deleted file mode 100644 index 8620b0d28..000000000 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/scopes/scope-internal-context/DialogManagerScopeInternalContext.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createScopeInternalContext } from '@/ui/utilities/recoil-scope/scopes-internal/utils/createScopeInternalContext'; -import { RecoilComponentStateKey } from '@/ui/utilities/state/component-state/types/RecoilComponentStateKey'; - -type DialogManagerScopeInternalContextProps = RecoilComponentStateKey; - -export const DialogManagerScopeInternalContext = - createScopeInternalContext(); diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/states/dialogInternalComponentState.ts b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/states/dialogInternalComponentState.ts new file mode 100644 index 000000000..49b04e8a5 --- /dev/null +++ b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/states/dialogInternalComponentState.ts @@ -0,0 +1,19 @@ +import { DialogComponentInstanceContext } from '@/ui/feedback/dialog-manager/contexts/DialogComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; +import { DialogOptions } from '../types/DialogOptions'; + +type DialogState = { + maxQueue: number; + queue: DialogOptions[]; +}; + +export const dialogInternalComponentState = createComponentStateV2( + { + key: 'dialogInternalComponentState', + defaultValue: { + maxQueue: 2, + queue: [], + }, + componentInstanceContext: DialogComponentInstanceContext, + }, +); diff --git a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/states/dialogInternalScopedState.ts b/packages/twenty-front/src/modules/ui/feedback/dialog-manager/states/dialogInternalScopedState.ts deleted file mode 100644 index 4f8c44a34..000000000 --- a/packages/twenty-front/src/modules/ui/feedback/dialog-manager/states/dialogInternalScopedState.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; - -import { DialogOptions } from '../types/DialogOptions'; - -type DialogState = { - maxQueue: number; - queue: DialogOptions[]; -}; - -export const dialogInternalScopedState = createComponentState({ - key: 'dialog/internal-state', - defaultValue: { - maxQueue: 2, - queue: [], - }, -});