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,
@@ -1,7 +1,7 @@
import { type FieldFilesValue } from '@/object-record/record-field/ui/types/FieldMetadata';
import { createState } from '@/ui/utilities/state/utils/createState';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const filePreviewState = createState<FieldFilesValue | null>({
export const filePreviewState = createStateV2<FieldFilesValue | null>({
key: 'filePreviewState',
defaultValue: null,
});
@@ -1,5 +1,5 @@
import styled from '@emotion/styled';
import { type ReactNode, useMemo, useState } from 'react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
@@ -10,16 +10,20 @@ import { arrayToChunks } from '~/utils/array/arrayToChunks';
import { ICON_PICKER_DROPDOWN_CONTENT_WIDTH } from '@/ui/input/components/constants/IconPickerDropdownContentWidth';
import { IconPickerScrollEffect } from '@/ui/input/hooks/IconPickerScrollEffect';
import { iconPickerVisibleCountState } from '@/ui/input/states/iconPickerVisibleCountState';
import {
ICON_PICKER_DEFAULT_VISIBLE_COUNT,
iconPickerVisibleCountState,
} from '@/ui/input/states/iconPickerVisibleCountState';
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
import { type DropdownOffset } from '@/ui/layout/dropdown/types/DropdownOffset';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilComponentValueV2';
import { t } from '@lingui/core/macro';
import { useRecoilValue, useResetRecoilState } from 'recoil';
import { useStore } from 'jotai';
import { IconApps, type IconComponent, useIcons } from 'twenty-ui/display';
import {
IconButton,
@@ -156,12 +160,18 @@ export const IconPicker = ({
const { closeDropdown } = useCloseDropdown();
const iconPickerVisibleCount =
useRecoilValue(iconPickerVisibleCountState(dropdownId)) ?? maxIconsVisible;
const store = useStore();
const resetIconPickerVisibleCount = useResetRecoilState(
iconPickerVisibleCountState(dropdownId),
);
const iconPickerVisibleCount =
useFamilyRecoilValueV2(iconPickerVisibleCountState, dropdownId) ??
maxIconsVisible;
const resetIconPickerVisibleCount = useCallback(() => {
store.set(
iconPickerVisibleCountState.atomFamily(dropdownId),
ICON_PICKER_DEFAULT_VISIBLE_COUNT,
);
}, [store, dropdownId]);
const { getIcons, getIcon } = useIcons();
const icons = getIcons();
@@ -1,7 +1,7 @@
import { iconPickerVisibleCountState } from '@/ui/input/states/iconPickerVisibleCountState';
import { useSetFamilyRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetFamilyRecoilStateV2';
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { iconPickerVisibleCountState } from '@/ui/input/states/iconPickerVisibleCountState';
type IconPickerScrollEffectProps = {
sentinelId: string;
@@ -14,8 +14,9 @@ export const IconPickerScrollEffect = ({
}: IconPickerScrollEffectProps) => {
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
const setIconPickerVisibleCount = useSetRecoilState(
iconPickerVisibleCountState(dropdownId),
const setIconPickerVisibleCount = useSetFamilyRecoilStateV2(
iconPickerVisibleCountState,
dropdownId,
);
useEffect(() => {
@@ -1,6 +1,8 @@
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2';
export const iconPickerVisibleCountState = createFamilyState<number, string>({
export const ICON_PICKER_DEFAULT_VISIBLE_COUNT = 25;
export const iconPickerVisibleCountState = createFamilyStateV2<number, string>({
key: 'iconPickerVisibleCountState',
defaultValue: 25,
defaultValue: ICON_PICKER_DEFAULT_VISIBLE_COUNT,
});
@@ -1,13 +1,13 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { type ReactNode } from 'react';
import { useRecoilValue } from 'recoil';
import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerCollapseButton';
import { PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID } from '@/ui/layout/page/constants/PageActionContainerClickOutsideId';
import { PAGE_BAR_MIN_HEIGHT } from '@/ui/layout/page/constants/PageBarMinHeight';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { AnimatePresence } from 'framer-motion';
import {
@@ -96,7 +96,7 @@ export const PageHeader = ({
}: PageHeaderProps) => {
const isMobile = useIsMobile();
const theme = useTheme();
const isNavigationDrawerExpanded = useRecoilValue(
const isNavigationDrawerExpanded = useRecoilValueV2(
isNavigationDrawerExpandedState,
);
@@ -1,13 +1,13 @@
import { useEffect } from 'react';
import { useRecoilValue } from 'recoil';
import {
NAVIGATION_DRAWER_WIDTH_VAR,
navigationDrawerWidthState,
} from '@/ui/navigation/states/navigationDrawerWidthState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
export const NavigationDrawerWidthEffect = () => {
const navigationDrawerWidth = useRecoilValue(navigationDrawerWidthState);
const navigationDrawerWidth = useRecoilValueV2(navigationDrawerWidthState);
useEffect(() => {
document.documentElement.style.setProperty(
@@ -9,13 +9,14 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useRecoilValue } from 'recoil';
import { useTheme } from '@emotion/react';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { Avatar } from 'twenty-ui/display';
export const MultiWorkspaceDropdownClickableComponent = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const theme = useTheme();
const isNavigationDrawerExpanded = useRecoilValue(
const isNavigationDrawerExpanded = useRecoilValueV2(
isNavigationDrawerExpandedState,
);
return (
@@ -1,9 +1,10 @@
import styled from '@emotion/styled';
import { type ReactNode, useState } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { useSetRecoilState } from 'recoil';
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
import { tableWidthResizeIsActiveState } from '@/object-record/record-table/states/tableWidthResizeIsActivedState';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { ResizablePanelEdge } from '@/ui/layout/resizable-panel/components/ResizablePanelEdge';
import { NAVIGATION_DRAWER_COLLAPSED_WIDTH } from '@/ui/layout/resizable-panel/constants/NavigationDrawerCollapsedWidth';
import { NAVIGATION_DRAWER_CONSTRAINTS } from '@/ui/layout/resizable-panel/constants/NavigationDrawerConstraints';
@@ -77,8 +78,8 @@ export const NavigationDrawer = ({
const isSettingsDrawer = useIsSettingsDrawer();
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
useRecoilState(isNavigationDrawerExpandedState);
const [navigationDrawerWidth, setNavigationDrawerWidth] = useRecoilState(
useRecoilStateV2(isNavigationDrawerExpandedState);
const [navigationDrawerWidth, setNavigationDrawerWidth] = useRecoilStateV2(
navigationDrawerWidthState,
);
const setTableWidthResizeIsActive = useSetRecoilState(
@@ -1,5 +1,6 @@
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
@@ -7,7 +8,6 @@ import {
motion,
type TargetAndTransition,
} from 'framer-motion';
import { useRecoilValue } from 'recoil';
const StyledAnimatedContainer = styled(motion.span)`
display: block;
@@ -20,7 +20,7 @@ export const NavigationDrawerAnimatedCollapseWrapper = ({
}) => {
const theme = useTheme();
const isSettingsPage = useIsSettingsPage();
const isNavigationDrawerExpanded = useRecoilValue(
const isNavigationDrawerExpanded = useRecoilValueV2(
isNavigationDrawerExpandedState,
);
@@ -1,11 +1,11 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { navigationDrawerExpandedMemorizedStateV2 } from '@/ui/navigation/states/navigationDrawerExpandedMemorizedStateV2';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { useIsWorkspaceActivationStatusEqualsTo } from '@/workspace/hooks/useIsWorkspaceActivationStatusEqualsTo';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { IconX } from 'twenty-ui/display';
@@ -47,9 +47,9 @@ export const NavigationDrawerBackButton = ({
title,
}: NavigationDrawerBackButtonProps) => {
const theme = useTheme();
const navigationMemorizedUrl = useRecoilValue(navigationMemorizedUrlState);
const navigationMemorizedUrl = useRecoilValueV2(navigationMemorizedUrlState);
const setIsNavigationDrawerExpanded = useSetRecoilState(
const setIsNavigationDrawerExpanded = useSetRecoilStateV2(
isNavigationDrawerExpandedState,
);
const navigationDrawerExpandedMemorized = useRecoilValueV2(
@@ -1,6 +1,6 @@
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import styled from '@emotion/styled';
import { useSetRecoilState } from 'recoil';
import {
IconLayoutSidebarLeftCollapse,
IconLayoutSidebarRightCollapse,
@@ -26,7 +26,7 @@ export const NavigationDrawerCollapseButton = ({
className,
direction = 'left',
}: NavigationDrawerCollapseButtonProps) => {
const setIsNavigationDrawerExpanded = useSetRecoilState(
const setIsNavigationDrawerExpanded = useSetRecoilStateV2(
isNavigationDrawerExpandedState,
);
@@ -1,11 +1,11 @@
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { MultiWorkspaceDropdownButton } from '@/ui/navigation/navigation-drawer/components/MultiWorkspaceDropdown/MultiWorkspaceDropdownButton';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { PAGE_BAR_MIN_HEIGHT } from '@/ui/layout/page/constants/PageBarMinHeight';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { NavigationDrawerCollapseButton } from './NavigationDrawerCollapseButton';
const StyledContainer = styled.div`
@@ -36,7 +36,7 @@ export const NavigationDrawerHeader = ({
}: NavigationDrawerHeaderProps) => {
const isMobile = useIsMobile();
const isNavigationDrawerExpanded = useRecoilValue(
const isNavigationDrawerExpanded = useRecoilValueV2(
isNavigationDrawerExpandedState,
);
@@ -6,13 +6,13 @@ import { useNavigationDrawerTooltip } from '@/ui/navigation/navigation-drawer/ho
import { type NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import isPropValid from '@emotion/is-prop-valid';
import { css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { type ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { Pill } from 'twenty-ui/components';
import {
@@ -305,7 +305,7 @@ export const NavigationDrawerItem = ({
const isMobile = useIsMobile();
const isSettingsPage = useIsSettingsPage();
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
useRecoilState(isNavigationDrawerExpandedState);
useRecoilStateV2(isNavigationDrawerExpandedState);
const { navigationItemId } = useNavigationDrawerTooltip(label, to);
@@ -1,5 +1,6 @@
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import {
@@ -8,7 +9,6 @@ import {
type TargetAndTransition,
} from 'framer-motion';
import { type ReactNode } from 'react';
import { useRecoilValue } from 'recoil';
const StyledAnimationGroupContainer = styled(motion.div)``;
@@ -23,7 +23,7 @@ export const NavigationDrawerItemsCollapsableContainer = ({
}: NavigationDrawerItemsCollapsableContainerProps) => {
const theme = useTheme();
const isSettingsPage = useIsSettingsPage();
const isNavigationDrawerExpanded = useRecoilValue(
const isNavigationDrawerExpanded = useRecoilValueV2(
isNavigationDrawerExpandedState,
);
const isExpanded = isNavigationDrawerExpanded || isSettingsPage;
@@ -4,6 +4,7 @@ import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { NavigationDrawerSectionTitleSkeletonLoader } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSectionTitleSkeletonLoader';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilValueV2';
import styled from '@emotion/styled';
import React from 'react';
import { useRecoilValue } from 'recoil';
@@ -60,7 +61,7 @@ export const NavigationDrawerSectionTitle = ({
alwaysShowRightIcon = false,
}: NavigationDrawerSectionTitleProps) => {
const isMobile = useIsMobile();
const isNavigationDrawerExpanded = useRecoilValue(
const isNavigationDrawerExpanded = useRecoilValueV2(
isNavigationDrawerExpandedState,
);
const isSettingsPage = useIsSettingsPage();
@@ -1,45 +1,40 @@
import { useRecoilCallback } from 'recoil';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isNavigationSectionOpenFamilyState } from '@/ui/navigation/navigation-drawer/states/isNavigationSectionOpenFamilyState';
import { useFamilyRecoilValueV2 } from '@/ui/utilities/state/jotai/hooks/useFamilyRecoilValueV2';
export const useNavigationSection = (navigationSectionId: string) => {
const closeNavigationSection = useRecoilCallback(
({ set }) =>
() => {
set(isNavigationSectionOpenFamilyState(navigationSectionId), false);
},
[navigationSectionId],
const store = useStore();
const isNavigationSectionOpen = useFamilyRecoilValueV2(
isNavigationSectionOpenFamilyState,
navigationSectionId,
);
const openNavigationSection = useRecoilCallback(
({ set }) =>
() => {
set(isNavigationSectionOpenFamilyState(navigationSectionId), true);
},
[navigationSectionId],
);
const closeNavigationSection = useCallback(() => {
store.set(
isNavigationSectionOpenFamilyState.atomFamily(navigationSectionId),
false,
);
}, [store, navigationSectionId]);
const toggleNavigationSection = useRecoilCallback(
({ snapshot }) =>
() => {
const isNavigationSectionOpen = snapshot
.getLoadable(isNavigationSectionOpenFamilyState(navigationSectionId))
.getValue();
const openNavigationSection = useCallback(() => {
store.set(
isNavigationSectionOpenFamilyState.atomFamily(navigationSectionId),
true,
);
}, [store, navigationSectionId]);
if (isNavigationSectionOpen) {
closeNavigationSection();
} else {
openNavigationSection();
}
},
[closeNavigationSection, openNavigationSection, navigationSectionId],
);
const isNavigationSectionOpenState =
isNavigationSectionOpenFamilyState(navigationSectionId);
const toggleNavigationSection = useCallback(() => {
if (isNavigationSectionOpen) {
closeNavigationSection();
} else {
openNavigationSection();
}
}, [isNavigationSectionOpen, closeNavigationSection, openNavigationSection]);
return {
isNavigationSectionOpenState,
isNavigationSectionOpen,
closeNavigationSection,
openNavigationSection,
toggleNavigationSection,
@@ -1,8 +1,7 @@
import { atom } from 'recoil';
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const isAdvancedModeEnabledState = atom<boolean>({
export const isAdvancedModeEnabledState = createStateV2<boolean>({
key: 'isAdvancedModeEnabledAtom',
default: false,
effects: [localStorageEffect()],
defaultValue: false,
useLocalStorage: true,
});
@@ -1,11 +1,10 @@
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
import { createFamilyStateV2 } from '@/ui/utilities/state/jotai/utils/createFamilyStateV2';
export const isNavigationSectionOpenFamilyState = createFamilyState<
export const isNavigationSectionOpenFamilyState = createFamilyStateV2<
boolean,
string
>({
key: 'isNavigationSectionOpenFamilyState',
defaultValue: true,
effects: [localStorageEffect()],
useLocalStorage: true,
});
@@ -1,11 +1,10 @@
import { atom } from 'recoil';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
const isMobile = window.innerWidth <= MOBILE_VIEWPORT;
export const isNavigationDrawerExpandedState = atom({
export const isNavigationDrawerExpandedState = createStateV2<boolean>({
key: 'isNavigationDrawerExpanded',
default: !isMobile,
effects: [localStorageEffect()],
defaultValue: !isMobile,
useLocalStorage: true,
});
@@ -1,12 +1,10 @@
import { atom } from 'recoil';
import { NAVIGATION_DRAWER_CONSTRAINTS } from '@/ui/layout/resizable-panel/constants/NavigationDrawerConstraints';
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const NAVIGATION_DRAWER_WIDTH_VAR = '--navigation-drawer-width';
export const navigationDrawerWidthState = atom<number>({
export const navigationDrawerWidthState = createStateV2<number>({
key: 'navigationDrawerWidth',
default: NAVIGATION_DRAWER_CONSTRAINTS.default,
effects: [localStorageEffect()],
defaultValue: NAVIGATION_DRAWER_CONSTRAINTS.default,
useLocalStorage: true,
});
@@ -1,5 +1,5 @@
import { createState } from '@/ui/utilities/state/utils/createState';
export const navigationMemorizedUrlState = createState<string>({
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const navigationMemorizedUrlState = createStateV2<string>({
key: 'navigationMemorizedUrlState',
defaultValue: '/',
});
@@ -1,8 +1,7 @@
import { createState } from '@/ui/utilities/state/utils/createState';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const shouldNavigateBackToMemorizedUrlOnSaveState = createState<boolean>(
{
export const shouldNavigateBackToMemorizedUrlOnSaveState =
createStateV2<boolean>({
key: 'shouldNavigateBackToMemorizedUrlOnSaveState',
defaultValue: false,
},
);
});
@@ -3,7 +3,7 @@ import { createContext } from 'react';
import { useSystemColorScheme } from '@/ui/theme/hooks/useSystemColorScheme';
import { persistedColorSchemeState } from '@/ui/theme/states/persistedColorSchemeState';
import { useRecoilState } from 'recoil';
import { useRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useRecoilStateV2';
import { type ColorScheme } from 'twenty-ui/input';
import { THEME_DARK, THEME_LIGHT, ThemeContextProvider } from 'twenty-ui/theme';
@@ -16,7 +16,7 @@ export const ThemeSchemeContext = createContext<(theme: ColorScheme) => void>(
);
export const BaseThemeProvider = ({ children }: BaseThemeProviderProps) => {
const [persistedColorScheme, setPersistedColorScheme] = useRecoilState(
const [persistedColorScheme, setPersistedColorScheme] = useRecoilStateV2(
persistedColorSchemeState,
);
const systemColorScheme = useSystemColorScheme();
@@ -1,10 +1,11 @@
import { useCallback } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { useRecoilState } from 'recoil';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { persistedColorSchemeState } from '@/ui/theme/states/persistedColorSchemeState';
import { useSetRecoilStateV2 } from '@/ui/utilities/state/jotai/hooks/useSetRecoilStateV2';
import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import {
type IconComponent,
@@ -19,7 +20,9 @@ export const useColorScheme = () => {
);
const { updateOneRecord } = useUpdateOneRecord();
const setPersistedColorScheme = useSetRecoilState(persistedColorSchemeState);
const setPersistedColorScheme = useSetRecoilStateV2(
persistedColorSchemeState,
);
const colorScheme = currentWorkspaceMember?.colorScheme ?? 'System';
@@ -1,10 +1,8 @@
import { atom } from 'recoil';
import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { localStorageEffect } from '~/utils/recoil/localStorageEffect';
import { createStateV2 } from '@/ui/utilities/state/jotai/utils/createStateV2';
export const persistedColorSchemeState = atom<ColorScheme>({
export const persistedColorSchemeState = createStateV2<ColorScheme>({
key: 'persistedColorSchemeState',
default: 'System',
effects: [localStorageEffect()],
defaultValue: 'System',
useLocalStorage: true,
});
@@ -1,13 +1,16 @@
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { type FamilyStateV2 } from '@/ui/utilities/state/jotai/types/FamilyStateV2';
export const createFamilyStateV2 = <ValueType, FamilyKey>({
key,
defaultValue,
useLocalStorage = false,
}: {
key: string;
defaultValue: ValueType;
useLocalStorage?: boolean;
}): FamilyStateV2<ValueType, FamilyKey> => {
const atomCache = new Map<
string,
@@ -26,8 +29,11 @@ export const createFamilyStateV2 = <ValueType, FamilyKey>({
return existing;
}
const baseAtom = atom(defaultValue);
baseAtom.debugLabel = `${key}__${cacheKey}`;
const atomKey = `${key}__${cacheKey}`;
const baseAtom = useLocalStorage
? atomWithStorage<ValueType>(atomKey, defaultValue)
: atom(defaultValue);
baseAtom.debugLabel = atomKey;
atomCache.set(cacheKey, baseAtom);
return baseAtom;
@@ -1,15 +1,20 @@
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { type StateV2 } from '@/ui/utilities/state/jotai/types/StateV2';
export const createStateV2 = <ValueType>({
key,
defaultValue,
useLocalStorage = false,
}: {
key: string;
defaultValue: ValueType;
useLocalStorage?: boolean;
}): StateV2<ValueType> => {
const baseAtom = atom(defaultValue);
const baseAtom = useLocalStorage
? atomWithStorage<ValueType>(key, defaultValue)
: atom(defaultValue);
baseAtom.debugLabel = key;
return {