Migrate more to Jotai (#18087)

Continue jotai migration
This commit is contained in:
Charles Bochet
2026-02-19 13:50:21 +01:00
committed by GitHub
parent 5a46cf7bd3
commit 530f74e28c
63 changed files with 330 additions and 283 deletions
@@ -1,12 +1,14 @@
import { dialogInternalComponentState } from '@/ui/feedback/dialog-manager/states/dialogInternalComponentState';
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { Dialog } from './Dialog';
import { DialogManagerEffect } from './DialogManagerEffect';
export const DialogManager = ({ children }: React.PropsWithChildren) => {
const dialogInternal = useRecoilComponentValue(dialogInternalComponentState);
const dialogInternal = useRecoilComponentValueV2(
dialogInternalComponentState,
);
const { closeDialog } = useDialogManager();
return (
@@ -5,10 +5,12 @@ import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePush
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { dialogInternalComponentState } from '@/ui/feedback/dialog-manager/states/dialogInternalComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
export const DialogManagerEffect = () => {
const dialogInternal = useRecoilComponentValue(dialogInternalComponentState);
const dialogInternal = useRecoilComponentValueV2(
dialogInternalComponentState,
);
const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack();
@@ -1,4 +1,5 @@
import { act, renderHook } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';
import { RecoilRoot } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
@@ -6,7 +7,11 @@ import { DialogComponentInstanceContext } from '@/ui/feedback/dialog-manager/con
import { useDialogManager } from '@/ui/feedback/dialog-manager/hooks/useDialogManager';
import { dialogInternalComponentState } from '@/ui/feedback/dialog-manager/states/dialogInternalComponentState';
import { type DialogOptions } from '@/ui/feedback/dialog-manager/types/DialogOptions';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import {
jotaiStore,
resetJotaiStore,
} from '@/ui/utilities/state/jotai/jotaiStore';
const mockedUuid = 'mocked-uuid';
jest.mock('uuid');
@@ -15,11 +20,13 @@ jest.mock('uuid');
const Wrapper = ({ children }: { children: React.ReactNode }) => (
<RecoilRoot>
<DialogComponentInstanceContext.Provider
value={{ instanceId: 'dialog-manager' }}
>
{children}
</DialogComponentInstanceContext.Provider>
<JotaiProvider store={jotaiStore}>
<DialogComponentInstanceContext.Provider
value={{ instanceId: 'dialog-manager' }}
>
{children}
</DialogComponentInstanceContext.Provider>
</JotaiProvider>
</RecoilRoot>
);
@@ -77,7 +84,7 @@ const renderHooks = () => {
const { result } = renderHook(
() => ({
dialogManager: useDialogManager(),
dialogInternal: useRecoilComponentValue(dialogInternalComponentState),
dialogInternal: useRecoilComponentValueV2(dialogInternalComponentState),
}),
renderHookConfig,
);
@@ -95,6 +102,10 @@ const expectedReturnFromEnqueue = (
};
describe('useDialogManager', () => {
beforeEach(() => {
resetJotaiStore();
});
describe('tests for useDialogManager - enqueueDialog', () => {
it('Should enqueueDialog', () => {
const result = renderHooks();
@@ -1,4 +1,4 @@
import { useRecoilCallback } from 'recoil';
import { useCallback } from 'react';
import { v4 } from 'uuid';
import { DIALOG_FOCUS_ID } from '@/ui/feedback/dialog-manager/constants/DialogFocusId';
@@ -8,55 +8,56 @@ import { DialogComponentInstanceContext } from '@/ui/feedback/dialog-manager/con
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { dialogInternalComponentState } from '@/ui/feedback/dialog-manager/states/dialogInternalComponentState';
import { type DialogOptions } from '@/ui/feedback/dialog-manager/types/DialogOptions';
import { useStore } from 'jotai';
export const useDialogManager = () => {
const componentInstanceId = useAvailableComponentInstanceIdOrThrow(
DialogComponentInstanceContext,
);
const store = useStore();
const { removeFocusItemFromFocusStackById } =
useRemoveFocusItemFromFocusStackById();
const closeDialog = useRecoilCallback(
({ set }) =>
(id: string) => {
set(
dialogInternalComponentState.atomFamily({
instanceId: componentInstanceId,
}),
(prevState) => ({
...prevState,
queue: prevState.queue.filter((dialog) => dialog.id !== id),
}),
);
const closeDialog = useCallback(
(id: string) => {
store.set(
dialogInternalComponentState.atomFamily({
instanceId: componentInstanceId,
}),
(prevState) => ({
...prevState,
queue: prevState.queue.filter((dialog) => dialog.id !== id),
}),
);
removeFocusItemFromFocusStackById({ focusId: DIALOG_FOCUS_ID });
},
[componentInstanceId, removeFocusItemFromFocusStackById],
removeFocusItemFromFocusStackById({ focusId: DIALOG_FOCUS_ID });
},
[componentInstanceId, removeFocusItemFromFocusStackById, store],
);
const setDialogQueue = useRecoilCallback(
({ set }) =>
(newValue) =>
set(
dialogInternalComponentState.atomFamily({
instanceId: componentInstanceId,
}),
(prev) => {
if (prev.queue.length >= prev.maxQueue) {
return {
...prev,
queue: [...prev.queue.slice(1), newValue] as DialogOptions[],
};
}
const setDialogQueue = useCallback(
(newValue: DialogOptions) =>
store.set(
dialogInternalComponentState.atomFamily({
instanceId: componentInstanceId,
}),
(prev) => {
if (prev.queue.length >= prev.maxQueue) {
return {
...prev,
queue: [...prev.queue, newValue] as DialogOptions[],
queue: [...prev.queue.slice(1), newValue],
};
},
),
[componentInstanceId],
}
return {
...prev,
queue: [...prev.queue, newValue],
};
},
),
[componentInstanceId, store],
);
const enqueueDialog = (options?: Omit<DialogOptions, 'id'>) => {
@@ -1,5 +1,5 @@
import { DialogComponentInstanceContext } from '@/ui/feedback/dialog-manager/contexts/DialogComponentInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { createComponentStateV2 } from '@/ui/utilities/state/jotai/utils/createComponentStateV2';
import { type DialogOptions } from '@/ui/feedback/dialog-manager/types/DialogOptions';
type DialogState = {
@@ -7,11 +7,13 @@ type DialogState = {
queue: DialogOptions[];
};
export const dialogInternalComponentState = createComponentState<DialogState>({
key: 'dialogInternalComponentState',
defaultValue: {
maxQueue: 2,
queue: [],
export const dialogInternalComponentState = createComponentStateV2<DialogState>(
{
key: 'dialogInternalComponentState',
defaultValue: {
maxQueue: 2,
queue: [],
},
componentInstanceContext: DialogComponentInstanceContext,
},
componentInstanceContext: DialogComponentInstanceContext,
});
);
@@ -6,7 +6,7 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { snackBarInternalComponentState } from '@/ui/feedback/snack-bar-manager/states/snackBarInternalComponentState';
import { RootStackingContextZIndices } from '@/ui/layout/constants/RootStackingContextZIndices';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { SnackBar } from './SnackBar';
@@ -27,7 +27,7 @@ const StyledSnackBarContainer = styled.div`
`;
export const SnackBarProvider = ({ children }: React.PropsWithChildren) => {
const snackBarInternal = useRecoilComponentValue(
const snackBarInternal = useRecoilComponentValueV2(
snackBarInternalComponentState,
);
@@ -1,5 +1,4 @@
import { useCallback } from 'react';
import { useRecoilCallback } from 'recoil';
import { v4 as uuidv4 } from 'uuid';
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
@@ -12,6 +11,7 @@ import { buildErrorAction } from '@/ui/feedback/snack-bar-manager/utils/build-er
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { type ApolloError } from '@apollo/client';
import { t } from '@lingui/core/macro';
import { useStore } from 'jotai';
import { isDefined } from 'twenty-shared/utils';
import { getErrorMessageFromApolloError } from '~/utils/get-error-message-from-apollo-error.util';
@@ -20,53 +20,53 @@ export const useSnackBar = () => {
SnackBarComponentInstanceContext,
);
const handleSnackBarClose = useRecoilCallback(
({ set }) =>
(id: string) => {
set(
snackBarInternalComponentState.atomFamily({
instanceId: componentInstanceId,
}),
(prevState) => ({
...prevState,
queue: prevState.queue.filter((snackBar) => snackBar.id !== id),
}),
);
},
[componentInstanceId],
const store = useStore();
const handleSnackBarClose = useCallback(
(id: string) => {
store.set(
snackBarInternalComponentState.atomFamily({
instanceId: componentInstanceId,
}),
(prevState) => ({
...prevState,
queue: prevState.queue.filter((snackBar) => snackBar.id !== id),
}),
);
},
[componentInstanceId, store],
);
const setSnackBarQueue = useRecoilCallback(
({ set }) =>
(newValue: SnackBarOptions) =>
set(
snackBarInternalComponentState.atomFamily({
instanceId: componentInstanceId,
}),
(prev) => {
if (
isDefined(newValue.dedupeKey) &&
prev.queue.some(
(snackBar) => snackBar.dedupeKey === newValue.dedupeKey,
)
) {
return prev;
}
if (prev.queue.length >= prev.maxQueue) {
return {
...prev,
queue: [...prev.queue.slice(1), newValue] as SnackBarOptions[],
};
}
const setSnackBarQueue = useCallback(
(newValue: SnackBarOptions) =>
store.set(
snackBarInternalComponentState.atomFamily({
instanceId: componentInstanceId,
}),
(prev) => {
if (
isDefined(newValue.dedupeKey) &&
prev.queue.some(
(snackBar) => snackBar.dedupeKey === newValue.dedupeKey,
)
) {
return prev;
}
if (prev.queue.length >= prev.maxQueue) {
return {
...prev,
queue: [...prev.queue, newValue] as SnackBarOptions[],
queue: [...prev.queue.slice(1), newValue] as SnackBarOptions[],
};
},
),
[componentInstanceId],
}
return {
...prev,
queue: [...prev.queue, newValue] as SnackBarOptions[],
};
},
),
[componentInstanceId, store],
);
const enqueueSuccessSnackBar = useCallback(
@@ -1,5 +1,5 @@
import { SnackBarComponentInstanceContext } from '@/ui/feedback/snack-bar-manager/contexts/SnackBarComponentInstanceContext';
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { createComponentStateV2 } from '@/ui/utilities/state/jotai/utils/createComponentStateV2';
import { type SnackBarProps } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
export type SnackBarOptions = SnackBarProps & {
@@ -12,7 +12,7 @@ export type SnackBarState = {
};
export const snackBarInternalComponentState =
createComponentState<SnackBarState>({
createComponentStateV2<SnackBarState>({
key: 'snackBarState',
defaultValue: {
maxQueue: 3,