Refacto views (#10272)
In this huge (sorry!) PR: - introducing objectMetadataItem in contextStore instead of objectMetadataId which is more convenient - splitting some big hooks into smaller parts to avoid re-renders - removing Effects to avoid re-renders (especially onViewChange) - making the view prefetch separate from favorites to avoid re-renders - making the view prefetch load a state and add selectors on top of it to avoir re-renders As a result, the performance is WAY better (I suspect the favorite implementation to trigger a lot of re-renders unfortunately). However, we are still facing a random app freeze on view creation. I could not investigate the root cause. As this seems to be already there in the precedent release, we can move forward but this seems a urgent follow up to me ==> EDIT: I've found the root cause after a few ours of deep dive... an infinite loop in RecordTableNoRecordGroupBodyEffect... prastoin edit: close https://github.com/twentyhq/twenty/issues/10253 --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
@ -1,47 +1,13 @@
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useReadFindManyRecordsQueryInCache } from '@/object-record/cache/hooks/useReadFindManyRecordsQueryInCache';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
|
||||
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
|
||||
export const useDeleteFavoriteFolder = () => {
|
||||
const { deleteOneRecord } = useDeleteOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
|
||||
});
|
||||
|
||||
const { upsertRecordsInCache } =
|
||||
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
|
||||
prefetchKey: PrefetchKey.AllFavorites,
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular:
|
||||
PREFETCH_CONFIG[PrefetchKey.AllFavorites].objectNameSingular,
|
||||
});
|
||||
|
||||
const { readFindManyRecordsQueryInCache } =
|
||||
useReadFindManyRecordsQueryInCache({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const deleteFavoriteFolder = async (folderId: string): Promise<void> => {
|
||||
await deleteOneRecord(folderId);
|
||||
|
||||
const allFavorites = readFindManyRecordsQueryInCache<Favorite>({
|
||||
queryVariables: {},
|
||||
recordGqlFields: PREFETCH_CONFIG[
|
||||
PrefetchKey.AllFavorites
|
||||
].operationSignatureFactory({ objectMetadataItem }).fields,
|
||||
});
|
||||
|
||||
const updatedFavorites = allFavorites.filter(
|
||||
(favorite) => favorite.favoriteFolderId !== folderId,
|
||||
);
|
||||
|
||||
upsertRecordsInCache(updatedFavorites);
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
import { favoriteViewsWithMinimalDataSelector } from '@/favorites/states/selectors/favoriteViewsWithMinimalDataSelector';
|
||||
import { sortFavorites } from '@/favorites/utils/sortFavorites';
|
||||
import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/hooks/useGetObjectRecordIdentifierByNameSingular';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { View } from '@/views/types/View';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@ -13,7 +11,9 @@ import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
|
||||
|
||||
export const useFavorites = () => {
|
||||
const { favorites } = usePrefetchedFavoritesData();
|
||||
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
|
||||
const favoriteViewsWithMinimalData = useRecoilValue(
|
||||
favoriteViewsWithMinimalDataSelector,
|
||||
);
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { objectMetadataItem: favoriteObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
@ -40,14 +40,14 @@ export const useFavorites = () => {
|
||||
favoriteRelationFieldMetadataItems,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
true,
|
||||
views,
|
||||
favoriteViewsWithMinimalData,
|
||||
objectMetadataItems,
|
||||
),
|
||||
[
|
||||
favorites,
|
||||
favoriteRelationFieldMetadataItems,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
views,
|
||||
favoriteViewsWithMinimalData,
|
||||
objectMetadataItems,
|
||||
],
|
||||
);
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { sortFavorites } from '@/favorites/utils/sortFavorites';
|
||||
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useFavoritesMetadata } from './useFavoritesMetadata';
|
||||
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
|
||||
import { usePrefetchedFavoritesFoldersData } from './usePrefetchedFavoritesFoldersData';
|
||||
@ -7,12 +9,13 @@ export const useFavoritesByFolder = () => {
|
||||
const { favorites } = usePrefetchedFavoritesData();
|
||||
const { favoriteFolders } = usePrefetchedFavoritesFoldersData();
|
||||
const {
|
||||
views,
|
||||
objectMetadataItems,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
favoriteRelationFields,
|
||||
} = useFavoritesMetadata();
|
||||
|
||||
const prefetchViews = useRecoilValue(prefetchViewsState);
|
||||
|
||||
const favoritesByFolder = favoriteFolders.map((folder) => ({
|
||||
folderId: folder.id,
|
||||
folderName: folder.name,
|
||||
@ -21,7 +24,7 @@ export const useFavoritesByFolder = () => {
|
||||
favoriteRelationFields,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
true,
|
||||
views,
|
||||
prefetchViews,
|
||||
objectMetadataItems,
|
||||
),
|
||||
}));
|
||||
|
||||
@ -2,14 +2,10 @@ import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/ho
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { View } from '@/views/types/View';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useFavoritesMetadata = () => {
|
||||
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const getObjectRecordIdentifierByNameSingular =
|
||||
useGetObjectRecordIdentifierByNameSingular();
|
||||
@ -27,7 +23,6 @@ export const useFavoritesMetadata = () => {
|
||||
);
|
||||
|
||||
return {
|
||||
views,
|
||||
objectMetadataItems,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
favoriteRelationFields,
|
||||
|
||||
@ -1,46 +1,30 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
type PrefetchedFavoritesData = {
|
||||
favorites: Favorite[];
|
||||
workspaceFavorites: Favorite[];
|
||||
upsertFavorites: (records: Favorite[]) => void;
|
||||
currentWorkspaceMemberId: string | undefined;
|
||||
};
|
||||
|
||||
export const usePrefetchedFavoritesData = (): PrefetchedFavoritesData => {
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const currentWorkspaceMemberId = currentWorkspaceMember?.id;
|
||||
const { records: _favorites } = usePrefetchedData<Favorite>(
|
||||
PrefetchKey.AllFavorites,
|
||||
{
|
||||
workspaceMemberId: {
|
||||
eq: currentWorkspaceMemberId,
|
||||
},
|
||||
},
|
||||
);
|
||||
const prefetchFavorites = useRecoilValue(prefetchFavoritesState);
|
||||
|
||||
const favorites = _favorites.filter(
|
||||
const favorites = prefetchFavorites.filter(
|
||||
(favorite) => favorite.workspaceMemberId === currentWorkspaceMemberId,
|
||||
);
|
||||
|
||||
const workspaceFavorites = _favorites.filter(
|
||||
const workspaceFavorites = prefetchFavorites.filter(
|
||||
(favorite) => favorite.workspaceMemberId === null,
|
||||
);
|
||||
|
||||
const { upsertRecordsInCache: upsertFavorites } =
|
||||
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
|
||||
prefetchKey: PrefetchKey.AllFavorites,
|
||||
});
|
||||
|
||||
return {
|
||||
favorites,
|
||||
workspaceFavorites,
|
||||
upsertFavorites,
|
||||
currentWorkspaceMemberId,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
|
||||
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { prefetchFavoriteFoldersState } from '@/prefetch/states/prefetchFavoriteFoldersState';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
type PrefetchedFavoritesFoldersData = {
|
||||
favoriteFolders: FavoriteFolder[];
|
||||
upsertFavoriteFolders: (records: FavoriteFolder[]) => void;
|
||||
currentWorkspaceMemberId: string | undefined;
|
||||
};
|
||||
|
||||
@ -16,23 +13,12 @@ export const usePrefetchedFavoritesFoldersData =
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const currentWorkspaceMemberId = currentWorkspaceMember?.id;
|
||||
|
||||
const { records: favoriteFolders } = usePrefetchedData<FavoriteFolder>(
|
||||
PrefetchKey.AllFavoritesFolders,
|
||||
{
|
||||
workspaceMemberId: {
|
||||
eq: currentWorkspaceMemberId,
|
||||
},
|
||||
},
|
||||
const prefetchFavoriteFolders = useRecoilValue(
|
||||
prefetchFavoriteFoldersState,
|
||||
);
|
||||
|
||||
const { upsertRecordsInCache: upsertFavoriteFolders } =
|
||||
useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
|
||||
prefetchKey: PrefetchKey.AllFavoritesFolders,
|
||||
});
|
||||
|
||||
return {
|
||||
favoriteFolders,
|
||||
upsertFavoriteFolders,
|
||||
favoriteFolders: prefetchFavoriteFolders,
|
||||
currentWorkspaceMemberId,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,32 +1,35 @@
|
||||
import { sortFavorites } from '@/favorites/utils/sortFavorites';
|
||||
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useFavoritesMetadata } from './useFavoritesMetadata';
|
||||
import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
|
||||
|
||||
export const useSortedFavorites = () => {
|
||||
const { favorites, workspaceFavorites } = usePrefetchedFavoritesData();
|
||||
const {
|
||||
views,
|
||||
objectMetadataItems,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
favoriteRelationFields,
|
||||
} = useFavoritesMetadata();
|
||||
|
||||
const prefetchViews = useRecoilValue(prefetchViewsState);
|
||||
|
||||
const favoritesSorted = useMemo(() => {
|
||||
return sortFavorites(
|
||||
favorites,
|
||||
favoriteRelationFields,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
true,
|
||||
views,
|
||||
prefetchViews,
|
||||
objectMetadataItems,
|
||||
);
|
||||
}, [
|
||||
favoriteRelationFields,
|
||||
favorites,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
views,
|
||||
objectMetadataItems,
|
||||
prefetchViews,
|
||||
]);
|
||||
|
||||
const workspaceFavoritesSorted = useMemo(() => {
|
||||
@ -35,14 +38,14 @@ export const useSortedFavorites = () => {
|
||||
favoriteRelationFields,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
false,
|
||||
views,
|
||||
prefetchViews,
|
||||
objectMetadataItems,
|
||||
);
|
||||
}, [
|
||||
workspaceFavorites,
|
||||
favoriteRelationFields,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
workspaceFavorites,
|
||||
views,
|
||||
prefetchViews,
|
||||
objectMetadataItems,
|
||||
]);
|
||||
|
||||
|
||||
@ -4,9 +4,7 @@ import { useGetObjectRecordIdentifierByNameSingular } from '@/object-metadata/ho
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { usePrefetchedData } from '@/prefetch/hooks/usePrefetchedData';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { View } from '@/views/types/View';
|
||||
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
@ -14,8 +12,7 @@ import { usePrefetchedFavoritesData } from './usePrefetchedFavoritesData';
|
||||
|
||||
export const useWorkspaceFavorites = () => {
|
||||
const { workspaceFavorites } = usePrefetchedFavoritesData();
|
||||
|
||||
const { records: views } = usePrefetchedData<View>(PrefetchKey.AllViews);
|
||||
const prefetchViews = useRecoilValue(prefetchViewsState);
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
const { objectMetadataItem: favoriteObjectMetadataItem } =
|
||||
useObjectMetadataItem({
|
||||
@ -42,14 +39,14 @@ export const useWorkspaceFavorites = () => {
|
||||
favoriteRelationFieldMetadataItems,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
false,
|
||||
views,
|
||||
prefetchViews,
|
||||
objectMetadataItems,
|
||||
),
|
||||
[
|
||||
workspaceFavorites,
|
||||
favoriteRelationFieldMetadataItems,
|
||||
getObjectRecordIdentifierByNameSingular,
|
||||
views,
|
||||
prefetchViews,
|
||||
objectMetadataItems,
|
||||
],
|
||||
);
|
||||
@ -59,7 +56,7 @@ export const useWorkspaceFavorites = () => {
|
||||
);
|
||||
|
||||
const favoriteViewObjectMetadataIds = new Set(
|
||||
views.reduce<string[]>((acc, view) => {
|
||||
prefetchViews.reduce<string[]>((acc, view) => {
|
||||
if (workspaceFavoriteIds.has(view.id)) {
|
||||
acc.push(view.objectMetadataId);
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
|
||||
import { View } from '@/views/types/View';
|
||||
import { selector } from 'recoil';
|
||||
|
||||
export const favoriteViewsWithMinimalDataSelector = selector<
|
||||
Pick<View, 'id' | 'name' | 'objectMetadataId' | 'icon'>[]
|
||||
>({
|
||||
key: 'favoriteViewsWithMinimalDataSelector',
|
||||
get: ({ get }) => {
|
||||
const views = get(prefetchViewsState);
|
||||
return views.map((view) => ({
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
objectMetadataId: view.objectMetadataId,
|
||||
icon: view.icon,
|
||||
}));
|
||||
},
|
||||
});
|
||||
@ -4,23 +4,13 @@ import { isDefined } from 'twenty-shared';
|
||||
|
||||
type ReturnType = {
|
||||
labelPlural: string;
|
||||
view: View | null;
|
||||
view: Pick<View, 'id' | 'name' | 'objectMetadataId'>;
|
||||
};
|
||||
|
||||
export const getObjectMetadataLabelPluralFromViewId = (
|
||||
views: View[],
|
||||
view: Pick<View, 'id' | 'name' | 'objectMetadataId'>,
|
||||
objectMetadataItems: ObjectMetadataItem[],
|
||||
viewId: string,
|
||||
): ReturnType => {
|
||||
const view = views.find((view) => view.id === viewId);
|
||||
|
||||
if (!view) {
|
||||
return {
|
||||
labelPlural: '',
|
||||
view: null,
|
||||
};
|
||||
}
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) => objectMetadataItem.id === view.objectMetadataId,
|
||||
);
|
||||
|
||||
@ -22,16 +22,23 @@ export const sortFavorites = (
|
||||
objectNameSingular: string,
|
||||
) => ObjectRecordIdentifier,
|
||||
hasLinkToShowPage: boolean,
|
||||
views: View[],
|
||||
views: Pick<View, 'id' | 'name' | 'objectMetadataId' | 'icon'>[],
|
||||
objectMetadataItems: ObjectMetadataItem[],
|
||||
) => {
|
||||
return favorites
|
||||
.map((favorite) => {
|
||||
if (isDefined(favorite.viewId) && isDefined(favorite.workspaceMemberId)) {
|
||||
const { labelPlural, view } = getObjectMetadataLabelPluralFromViewId(
|
||||
views,
|
||||
const view = views.find((view) => view.id === favorite.viewId);
|
||||
|
||||
if (!isDefined(view)) {
|
||||
return {
|
||||
...favorite,
|
||||
} as ProcessedFavorite;
|
||||
}
|
||||
|
||||
const { labelPlural } = getObjectMetadataLabelPluralFromViewId(
|
||||
view,
|
||||
objectMetadataItems,
|
||||
favorite.viewId,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user