Simplify last visited (#10259)

In this PR, I'm simplifying the lastVisitedView / Object logic:
- removing fallback logic as it's not useful
- splitting hooks into smaller hooks (to avoir re-renders)
- removing componentState on those states that are global
This commit is contained in:
Charles Bochet
2025-02-17 17:27:28 +01:00
committed by GitHub
parent a526472ddc
commit 5b4cb4bd2c
22 changed files with 249 additions and 278 deletions

View File

@ -1,5 +1,5 @@
import { currentUserState } from '@/auth/states/currentUserState';
import { useLastVisitedObjectMetadataItem } from '@/navigation/hooks/useLastVisitedObjectMetadataItem';
import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState';
import { ObjectPathInfo } from '@/navigation/types/ObjectPathInfo';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
@ -16,8 +16,9 @@ export const useDefaultHomePagePath = () => {
const { activeObjectMetadataItems, alphaSortedActiveObjectMetadataItems } =
useFilteredObjectMetadataItems();
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
const { lastVisitedObjectMetadataItemId } =
useLastVisitedObjectMetadataItem();
const lastVisitedObjectMetadataItemId = useRecoilValue(
lastVisitedObjectMetadataItemIdState,
);
const getActiveObjectMetadataItemMatchingId = useCallback(
(objectMetadataId: string) => {

View File

@ -1,70 +0,0 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { lastVisitedObjectMetadataItemIdStateSelector } from '@/navigation/states/selectors/lastVisitedObjectMetadataItemIdStateSelector';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { AppPath } from '@/types/AppPath';
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { getAppPath } from '~/utils/navigation/getAppPath';
export const useLastVisitedObjectMetadataItem = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const scopeId = currentWorkspace?.id ?? '';
const lastVisitedObjectMetadataItemIdState = extractComponentState(
lastVisitedObjectMetadataItemIdStateSelector,
scopeId,
);
const [lastVisitedObjectMetadataItemId, setLastVisitedObjectMetadataItemId] =
useRecoilState(lastVisitedObjectMetadataItemIdState);
const {
findActiveObjectMetadataItemByNamePlural,
alphaSortedActiveObjectMetadataItems,
} = useFilteredObjectMetadataItems();
const setNavigationMemorizedUrl = useSetRecoilState(
navigationMemorizedUrlState,
);
const setFallbackForLastVisitedObjectMetadataItem = (
objectMetadataItemId: string,
) => {
const isDeactivateDefault = isDeeplyEqual(
lastVisitedObjectMetadataItemId,
objectMetadataItemId,
);
const [newFallbackObjectMetadataItem] =
alphaSortedActiveObjectMetadataItems.filter(
(item) => item.id !== objectMetadataItemId,
);
if (isDeactivateDefault) {
setLastVisitedObjectMetadataItemId(newFallbackObjectMetadataItem.id);
setNavigationMemorizedUrl(
getAppPath(AppPath.RecordIndexPage, {
objectNamePlural: newFallbackObjectMetadataItem.namePlural,
}),
);
}
};
const setLastVisitedObjectMetadataItem = (objectNamePlural: string) => {
const fallbackObjectMetadataItem =
findActiveObjectMetadataItemByNamePlural(objectNamePlural);
if (isDefined(fallbackObjectMetadataItem)) {
setLastVisitedObjectMetadataItemId(fallbackObjectMetadataItem.id);
}
};
return {
lastVisitedObjectMetadataItemId,
setLastVisitedObjectMetadataItem,
setFallbackForLastVisitedObjectMetadataItem,
};
};

View File

@ -1,54 +1,15 @@
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { lastVisitedViewPerObjectMetadataItemStateSelector } from '@/navigation/states/selectors/lastVisitedViewPerObjectMetadataItemStateSelector';
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
import { useRecoilState, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared';
import { useRecoilValue } from 'recoil';
export const useLastVisitedView = () => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const scopeId = currentWorkspace?.id ?? '';
const lastVisitedViewPerObjectMetadataItemState = extractComponentState(
lastVisitedViewPerObjectMetadataItemStateSelector,
scopeId,
const lastVisitedViewPerObjectMetadataItem = useRecoilValue(
lastVisitedViewPerObjectMetadataItemState,
);
const [
lastVisitedViewPerObjectMetadataItem,
setLastVisitedViewPerObjectMetadataItem,
] = useRecoilState(lastVisitedViewPerObjectMetadataItemState);
const { findActiveObjectMetadataItemByNamePlural } =
useFilteredObjectMetadataItems();
const setFallbackForLastVisitedView = (objectMetadataItemId: string) => {
/* ...{} allows us to pass value as undefined to remove that particular key
even though param type is of type Record<string,string> */
setLastVisitedViewPerObjectMetadataItem({
...{},
[objectMetadataItemId]: undefined,
});
};
const setLastVisitedView = ({
objectNamePlural,
viewId,
}: {
objectNamePlural: string;
viewId: string;
}) => {
const objectMetadataItem =
findActiveObjectMetadataItemByNamePlural(objectNamePlural);
if (isDefined(objectMetadataItem)) {
setLastVisitedViewPerObjectMetadataItem({
[objectMetadataItem.id]: viewId,
});
}
};
const getLastVisitedViewIdFromObjectNamePlural = (
objectNamePlural: string,
) => {
@ -65,9 +26,7 @@ export const useLastVisitedView = () => {
return lastVisitedViewPerObjectMetadataItem?.[objectMetadataItemId];
};
return {
setLastVisitedView,
getLastVisitedViewIdFromObjectNamePlural,
getLastVisitedViewIdFromObjectMetadataItemId,
setFallbackForLastVisitedView,
};
};

View File

@ -1,12 +1,12 @@
import { currentMobileNavigationDrawerState } from '@/navigation/states/currentMobileNavigationDrawerState';
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
import { useRecoilState } from 'recoil';
import { useSetRecoilState } from 'recoil';
export const useOpenSettingsMenu = () => {
const [, setIsNavigationDrawerExpanded] = useRecoilState(
const setIsNavigationDrawerExpanded = useSetRecoilState(
isNavigationDrawerExpandedState,
);
const [, setCurrentMobileNavigationDrawer] = useRecoilState(
const setCurrentMobileNavigationDrawer = useSetRecoilState(
currentMobileNavigationDrawerState,
);

View File

@ -0,0 +1,35 @@
import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared';
export const useSetLastVisitedObjectMetadataId = () => {
const setLastVisitedObjectMetadataId = useRecoilCallback(
({ set, snapshot }) =>
({ objectMetadataItemId }: { objectMetadataItemId: string }) => {
const objectMetadataItems = snapshot
.getLoadable(objectMetadataItemsState)
.getValue();
const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === objectMetadataItemId,
);
const lastVisitedObjectMetadataItemId = snapshot
.getLoadable(lastVisitedObjectMetadataItemIdState)
.getValue();
if (
isDefined(objectMetadataItem) &&
lastVisitedObjectMetadataItemId !== objectMetadataItemId
) {
set(lastVisitedObjectMetadataItemIdState, objectMetadataItemId);
}
},
[],
);
return {
setLastVisitedObjectMetadataId,
};
};

View File

@ -0,0 +1,63 @@
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { useLazyPrefetchedData } from '@/prefetch/hooks/useLazyPrefetchData';
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
import { View } from '@/views/types/View';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared';
export const useSetLastVisitedViewForObjectMetadataNamePlural = () => {
const { records: views, findManyRecords } = useLazyPrefetchedData<View>(
PrefetchKey.AllViews,
);
const setLastVisitedViewForObjectMetadataNamePlural = useRecoilCallback(
({ set, snapshot }) =>
async ({
objectNamePlural,
viewId,
}: {
objectNamePlural: string;
viewId: string;
}) => {
await findManyRecords();
const view = views.find((view: View) => view.id === viewId);
const objectMetadataItems = snapshot
.getLoadable(objectMetadataItemsState)
.getValue();
const objectMetadataItem = objectMetadataItems.find(
(item) => item.namePlural === objectNamePlural,
);
if (!isDefined(objectMetadataItem) || !isDefined(view)) {
return;
}
if (view.objectMetadataId !== objectMetadataItem.id) {
return;
}
const lastVisitedViewPerObjectMetadataItem = snapshot
.getLoadable(lastVisitedViewPerObjectMetadataItemState)
.getValue();
const lastVisitedViewId =
lastVisitedViewPerObjectMetadataItem?.[objectMetadataItem?.id];
if (isDefined(objectMetadataItem) && lastVisitedViewId !== viewId) {
set(lastVisitedViewPerObjectMetadataItemState, {
...lastVisitedViewPerObjectMetadataItem,
[objectMetadataItem.id]: viewId,
});
}
},
[findManyRecords, views],
);
return {
setLastVisitedViewForObjectMetadataNamePlural,
};
};

View File

@ -1,10 +1,7 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { createState } from 'twenty-ui';
import { localStorageEffect } from '~/utils/recoil-effects';
export const lastVisitedObjectMetadataItemIdState = createComponentState<Record<
string,
string
> | null>({
export const lastVisitedObjectMetadataItemIdState = createState<string | null>({
key: 'lastVisitedObjectMetadataItemIdState',
defaultValue: null,
effects: [localStorageEffect()],

View File

@ -1,9 +1,11 @@
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
import { createState } from 'twenty-ui';
import { localStorageEffect } from '~/utils/recoil-effects';
export const lastVisitedViewPerObjectMetadataItemState =
createComponentState<Record<string, string> | null>({
key: 'lastVisitedViewPerObjectMetadataItemState',
defaultValue: null,
effects: [localStorageEffect()],
});
export const lastVisitedViewPerObjectMetadataItemState = createState<Record<
string,
string
> | null>({
key: 'lastVisitedViewPerObjectMetadataItemState',
defaultValue: null,
effects: [localStorageEffect()],
});

View File

@ -1,24 +0,0 @@
import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVisitedObjectMetadataItemIdState';
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
export const lastVisitedObjectMetadataItemIdStateSelector =
createComponentSelector<string | null>({
key: 'lastVisitedObjectMetadataItemIdStateSelector',
get:
({ scopeId }: { scopeId: string }) =>
({ get }) => {
const state = get(lastVisitedObjectMetadataItemIdState({ scopeId }));
return state?.['last_visited_object']
? state['last_visited_object']
: null;
},
set:
({ scopeId }: { scopeId: string }) =>
({ set }, newValue) => {
set(lastVisitedObjectMetadataItemIdState({ scopeId }), {
...(typeof newValue === 'string' && {
last_visited_object: newValue,
}),
});
},
});

View File

@ -1,34 +0,0 @@
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
import { createComponentSelector } from '@/ui/utilities/state/component-state/utils/createComponentSelector';
import { isDefined } from 'twenty-shared';
export const lastVisitedViewPerObjectMetadataItemStateSelector =
createComponentSelector<Record<string, string> | null>({
key: 'lastVisitedViewPerObjectMetadataItemStateSelector',
get:
({ scopeId }: { scopeId: string }) =>
({ get }) => {
const state = get(
lastVisitedViewPerObjectMetadataItemState({ scopeId }),
);
if (isDefined(state?.['last_visited_object'])) {
const { last_visited_object: _last_visited_object, ...rest } = state;
return rest;
}
return state;
},
set:
({ scopeId }: { scopeId: string }) =>
({ set, get }, newValue) => {
const currentLastVisitedViewPerObjectMetadataItems = get(
lastVisitedViewPerObjectMetadataItemStateSelector({ scopeId }),
);
set(lastVisitedViewPerObjectMetadataItemState({ scopeId }), {
...currentLastVisitedViewPerObjectMetadataItems,
...newValue,
});
},
});