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,10 +1,12 @@
|
||||
import { PrefetchRunQueriesEffect } from '@/prefetch/components/PrefetchRunQueriesEffect';
|
||||
import { PrefetchRunFavoriteQueriesEffect } from '@/prefetch/components/PrefetchRunFavoriteQueriesEffect';
|
||||
import { PrefetchRunViewQueryEffect } from '@/prefetch/components/PrefetchRunViewQueryEffect';
|
||||
import React from 'react';
|
||||
|
||||
export const PrefetchDataProvider = ({ children }: React.PropsWithChildren) => {
|
||||
return (
|
||||
<>
|
||||
<PrefetchRunQueriesEffect />
|
||||
<PrefetchRunFavoriteQueriesEffect />
|
||||
<PrefetchRunViewQueryEffect />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -0,0 +1,110 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { findAllFavoritesFolderOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesFolderOperationSignatureFactory';
|
||||
import { findAllFavoritesOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllFavoritesOperationSignatureFactory';
|
||||
import { prefetchFavoriteFoldersState } from '@/prefetch/states/prefetchFavoriteFoldersState';
|
||||
import { prefetchFavoritesState } from '@/prefetch/states/prefetchFavoritesState';
|
||||
import { prefetchIsLoadedFamilyState } from '@/prefetch/states/prefetchIsLoadedFamilyState';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const PrefetchRunFavoriteQueriesEffect = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
|
||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const setIsPrefetchFavoritesLoaded = useSetRecoilState(
|
||||
prefetchIsLoadedFamilyState(PrefetchKey.AllFavorites),
|
||||
);
|
||||
|
||||
const setIsPrefetchFavoritesFoldersLoaded = useSetRecoilState(
|
||||
prefetchIsLoadedFamilyState(PrefetchKey.AllFavoritesFolders),
|
||||
);
|
||||
|
||||
const findAllFavoritesOperationSignature =
|
||||
findAllFavoritesOperationSignatureFactory({
|
||||
objectMetadataItem: objectMetadataItems.find(
|
||||
(item) => item.nameSingular === CoreObjectNameSingular.Favorite,
|
||||
),
|
||||
});
|
||||
|
||||
const findAllFavoriteFoldersOperationSignature =
|
||||
findAllFavoritesFolderOperationSignatureFactory({
|
||||
objectMetadataItem: objectMetadataItems.find(
|
||||
(item) => item.nameSingular === CoreObjectNameSingular.FavoriteFolder,
|
||||
),
|
||||
});
|
||||
|
||||
const { records: favorites } = useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.Favorite,
|
||||
filter: findAllFavoritesOperationSignature.variables.filter,
|
||||
recordGqlFields: findAllFavoritesOperationSignature.fields,
|
||||
skip: !currentUser || isWorkspaceSuspended,
|
||||
});
|
||||
|
||||
const { records: favoriteFolders } = useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.FavoriteFolder,
|
||||
filter: findAllFavoriteFoldersOperationSignature.variables.filter,
|
||||
recordGqlFields: findAllFavoriteFoldersOperationSignature.fields,
|
||||
skip: !currentUser || isWorkspaceSuspended,
|
||||
});
|
||||
|
||||
const setPrefetchFavoritesState = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(favorites: Favorite[]) => {
|
||||
const existingFavorites = snapshot
|
||||
.getLoadable(prefetchFavoritesState)
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(existingFavorites, favorites)) {
|
||||
set(prefetchFavoritesState, favorites);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const setPrefetchFavoriteFoldersState = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(favoriteFolders: FavoriteFolder[]) => {
|
||||
const existingFavoriteFolders = snapshot
|
||||
.getLoadable(prefetchFavoriteFoldersState)
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(existingFavoriteFolders, favoriteFolders)) {
|
||||
set(prefetchFavoriteFoldersState, favoriteFolders);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(favorites)) {
|
||||
setPrefetchFavoritesState(favorites as Favorite[]);
|
||||
setIsPrefetchFavoritesLoaded(true);
|
||||
}
|
||||
}, [favorites, setPrefetchFavoritesState, setIsPrefetchFavoritesLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(favoriteFolders)) {
|
||||
setPrefetchFavoriteFoldersState(favoriteFolders as FavoriteFolder[]);
|
||||
setIsPrefetchFavoritesFoldersLoaded(true);
|
||||
}
|
||||
}, [
|
||||
favoriteFolders,
|
||||
setPrefetchFavoriteFoldersState,
|
||||
setIsPrefetchFavoritesFoldersLoaded,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -1,70 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { Favorite } from '@/favorites/types/Favorite';
|
||||
import { FavoriteFolder } from '@/favorites/types/FavoriteFolder';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/useCombinedFindManyRecords';
|
||||
import { PREFETCH_CONFIG } from '@/prefetch/constants/PrefetchConfig';
|
||||
import { useUpsertRecordsInCacheForPrefetchKey } from '@/prefetch/hooks/internal/useUpsertRecordsInCacheForPrefetchKey';
|
||||
import { PrefetchKey } from '@/prefetch/types/PrefetchKey';
|
||||
import { View } from '@/views/types/View';
|
||||
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const PrefetchRunQueriesEffect = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
|
||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
|
||||
|
||||
const { upsertRecordsInCache: upsertViewsInCache } =
|
||||
useUpsertRecordsInCacheForPrefetchKey<View>({
|
||||
prefetchKey: PrefetchKey.AllViews,
|
||||
});
|
||||
|
||||
const { upsertRecordsInCache: upsertFavoritesInCache } =
|
||||
useUpsertRecordsInCacheForPrefetchKey<Favorite>({
|
||||
prefetchKey: PrefetchKey.AllFavorites,
|
||||
});
|
||||
const { upsertRecordsInCache: upsertFavoritesFoldersInCache } =
|
||||
useUpsertRecordsInCacheForPrefetchKey<FavoriteFolder>({
|
||||
prefetchKey: PrefetchKey.AllFavoritesFolders,
|
||||
});
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const operationSignatures = Object.values(PREFETCH_CONFIG)
|
||||
|
||||
.map(({ objectNameSingular, operationSignatureFactory }) => {
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.nameSingular === objectNameSingular,
|
||||
);
|
||||
|
||||
return operationSignatureFactory({ objectMetadataItem });
|
||||
});
|
||||
|
||||
const { result } = useCombinedFindManyRecords({
|
||||
operationSignatures,
|
||||
skip: !currentUser || isWorkspaceSuspended,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(result.views)) {
|
||||
upsertViewsInCache(result.views as View[]);
|
||||
}
|
||||
|
||||
if (isDefined(result.favorites)) {
|
||||
upsertFavoritesInCache(result.favorites as Favorite[]);
|
||||
}
|
||||
if (isDefined(result.favoriteFolders)) {
|
||||
upsertFavoritesFoldersInCache(result.favoriteFolders as FavoriteFolder[]);
|
||||
}
|
||||
}, [
|
||||
result,
|
||||
upsertViewsInCache,
|
||||
upsertFavoritesInCache,
|
||||
upsertFavoritesFoldersInCache,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -0,0 +1,59 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { findAllViewsOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllViewsOperationSignatureFactory';
|
||||
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
|
||||
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
|
||||
import { View } from '@/views/types/View';
|
||||
import { useIsWorkspaceActivationStatusSuspended } from '@/workspace/hooks/useIsWorkspaceActivationStatusSuspended';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const PrefetchRunViewQueryEffect = () => {
|
||||
const currentUser = useRecoilValue(currentUserState);
|
||||
|
||||
const isWorkspaceSuspended = useIsWorkspaceActivationStatusSuspended();
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const findAllViewsOperationSignature = findAllViewsOperationSignatureFactory({
|
||||
objectMetadataItem: objectMetadataItems.find(
|
||||
(item) => item.nameSingular === CoreObjectNameSingular.View,
|
||||
),
|
||||
});
|
||||
|
||||
const { records } = useFindManyRecords({
|
||||
objectNameSingular: CoreObjectNameSingular.View,
|
||||
filter: findAllViewsOperationSignature.variables.filter,
|
||||
recordGqlFields: findAllViewsOperationSignature.fields,
|
||||
skip: !currentUser || isWorkspaceSuspended,
|
||||
});
|
||||
|
||||
const setPrefetchViewsState = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(views: View[]) => {
|
||||
const existingViews = snapshot
|
||||
.getLoadable(prefetchViewsState)
|
||||
.getValue();
|
||||
|
||||
if (!isDeeplyEqual(existingViews, views)) {
|
||||
set(prefetchViewsState, views);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const isPersistingViewFields = useRecoilValue(isPersistingViewFieldsState);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(records) && !isPersistingViewFields) {
|
||||
setPrefetchViewsState(records as View[]);
|
||||
}
|
||||
}, [isPersistingViewFields, records, setPrefetchViewsState]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
Reference in New Issue
Block a user