Fix page change effect being rerun after changes on views (#12869)

`useDefaultHomePagePath` was rerendered each time a view was changed, so
the PageChangeEffect reran every time a view was updated, but we only
want this effect to run on page change.
This commit is contained in:
Raphaël Bosi
2025-06-25 15:09:00 +02:00
committed by GitHub
parent 0f106ab8e0
commit 6450e11f1e
4 changed files with 78 additions and 39 deletions

View File

@ -6,6 +6,7 @@ import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceSta
import { useDefaultHomePagePath } from '@/navigation/hooks/useDefaultHomePagePath';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
import { arePrefetchViewsLoadedState } from '@/prefetch/states/arePrefetchViewsLoaded';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { AppPath } from '@/types/AppPath';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
@ -31,8 +32,12 @@ const renderHooks = ({
objectMetadataItemsState,
);
const setPrefetchViews = useSetRecoilState(prefetchViewsState);
const setArePrefetchViewsLoaded = useSetRecoilState(
arePrefetchViewsLoadedState,
);
setObjectMetadataItems(generatedMockObjectMetadataItems);
setArePrefetchViewsLoaded(true);
if (withExistingView) {
setPrefetchViews([
@ -56,6 +61,8 @@ const renderHooks = ({
__typename: 'View',
},
]);
} else {
setPrefetchViews([]);
}
if (withCurrentUser) {
@ -70,6 +77,7 @@ const renderHooks = ({
);
return { result };
};
describe('useDefaultHomePagePath', () => {
it('should return proper path when no currentUser', () => {
const { result } = renderHooks({

View File

@ -3,12 +3,13 @@ import { lastVisitedObjectMetadataItemIdState } from '@/navigation/states/lastVi
import { ObjectPathInfo } from '@/navigation/types/ObjectPathInfo';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { arePrefetchViewsLoadedState } from '@/prefetch/states/arePrefetchViewsLoaded';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import isEmpty from 'lodash.isempty';
import { useCallback, useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { getAppPath } from '~/utils/navigation/getAppPath';
@ -21,6 +22,8 @@ export const useDefaultHomePagePath = () => {
alphaSortedActiveNonSystemObjectMetadataItems,
} = useFilteredObjectMetadataItems();
const arePrefetchViewsLoaded = useRecoilValue(arePrefetchViewsLoadedState);
const readableAlphaSortedActiveNonSystemObjectMetadataItems = useMemo(() => {
return alphaSortedActiveNonSystemObjectMetadataItems.filter((item) => {
const objectPermissions = objectPermissionsByObjectMetadataId[item.id];
@ -31,11 +34,6 @@ export const useDefaultHomePagePath = () => {
objectPermissionsByObjectMetadataId,
]);
const prefetchViews = useRecoilValue(prefetchViewsState);
const lastVisitedObjectMetadataItemId = useRecoilValue(
lastVisitedObjectMetadataItemIdState,
);
const getActiveObjectMetadataItemMatchingId = useCallback(
(objectMetadataId: string) => {
return activeNonSystemObjectMetadataItems.find(
@ -45,12 +43,21 @@ export const useDefaultHomePagePath = () => {
[activeNonSystemObjectMetadataItems],
);
const getFirstView = useCallback(
(objectMetadataItemId: string | undefined | null) =>
prefetchViews.find(
(view) => view.objectMetadataId === objectMetadataItemId,
),
[prefetchViews],
const getFirstView = useRecoilCallback(
({ snapshot }) => {
return (objectMetadataItemId: string | undefined | null) => {
if (!arePrefetchViewsLoaded) {
return undefined;
}
const views = snapshot.getLoadable(prefetchViewsState).getValue();
return views.find(
(view) => view.objectMetadataId === objectMetadataItemId,
);
};
},
[arePrefetchViewsLoaded],
);
const firstObjectPathInfo = useMemo<ObjectPathInfo | null>(() => {
@ -64,38 +71,51 @@ export const useDefaultHomePagePath = () => {
const view = getFirstView(firstObjectMetadataItem?.id);
return { objectMetadataItem: firstObjectMetadataItem, view };
}, [readableAlphaSortedActiveNonSystemObjectMetadataItems, getFirstView]);
}, [getFirstView, readableAlphaSortedActiveNonSystemObjectMetadataItems]);
const defaultObjectPathInfo = useMemo<ObjectPathInfo | null>(() => {
if (
!isDefined(lastVisitedObjectMetadataItemId) ||
!objectPermissionsByObjectMetadataId[lastVisitedObjectMetadataItemId]
?.canReadObjectRecords
) {
return firstObjectPathInfo;
}
const getDefaultObjectPathInfo = useRecoilCallback(
({ snapshot }) => {
return () => {
const lastVisitedObjectMetadataItemId = snapshot
.getLoadable(lastVisitedObjectMetadataItemIdState)
.getValue();
const lastVisitedObjectMetadataItem = getActiveObjectMetadataItemMatchingId(
lastVisitedObjectMetadataItemId,
);
if (
!isDefined(lastVisitedObjectMetadataItemId) ||
!objectPermissionsByObjectMetadataId[lastVisitedObjectMetadataItemId]
?.canReadObjectRecords
) {
return firstObjectPathInfo;
}
if (isDefined(lastVisitedObjectMetadataItem)) {
return {
view: getFirstView(lastVisitedObjectMetadataItemId),
objectMetadataItem: lastVisitedObjectMetadataItem,
const lastVisitedObjectMetadataItem =
getActiveObjectMetadataItemMatchingId(
lastVisitedObjectMetadataItemId,
);
if (isDefined(lastVisitedObjectMetadataItem)) {
return {
view: getFirstView(lastVisitedObjectMetadataItemId),
objectMetadataItem: lastVisitedObjectMetadataItem,
};
}
return firstObjectPathInfo;
};
}
return firstObjectPathInfo;
}, [
firstObjectPathInfo,
getActiveObjectMetadataItemMatchingId,
getFirstView,
lastVisitedObjectMetadataItemId,
objectPermissionsByObjectMetadataId,
]);
},
[
firstObjectPathInfo,
getActiveObjectMetadataItemMatchingId,
getFirstView,
objectPermissionsByObjectMetadataId,
],
);
const defaultHomePagePath = useMemo(() => {
if (!arePrefetchViewsLoaded) {
return undefined;
}
if (!isDefined(currentUser)) {
return AppPath.SignInUp;
}
@ -104,6 +124,8 @@ export const useDefaultHomePagePath = () => {
return `${AppPath.Settings}/${SettingsPath.ProfilePage}`;
}
const defaultObjectPathInfo = getDefaultObjectPathInfo();
if (!isDefined(defaultObjectPathInfo)) {
return AppPath.NotFound;
}
@ -117,8 +139,9 @@ export const useDefaultHomePagePath = () => {
viewId ? { viewId } : undefined,
);
}, [
arePrefetchViewsLoaded,
currentUser,
defaultObjectPathInfo,
getDefaultObjectPathInfo,
readableAlphaSortedActiveNonSystemObjectMetadataItems,
]);

View File

@ -6,6 +6,7 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { findAllViewsOperationSignatureFactory } from '@/prefetch/graphql/operation-signatures/factories/findAllViewsOperationSignatureFactory';
import { arePrefetchViewsLoadedState } from '@/prefetch/states/arePrefetchViewsLoaded';
import { prefetchViewsState } from '@/prefetch/states/prefetchViewsState';
import { isPersistingViewFieldsState } from '@/views/states/isPersistingViewFieldsState';
import { View } from '@/views/types/View';
@ -45,6 +46,7 @@ export const PrefetchRunViewQueryEffect = () => {
if (!isDeeplyEqual(existingViews, views)) {
set(prefetchViewsState, views);
set(arePrefetchViewsLoadedState, true);
}
},
[],

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui/utilities';
export const arePrefetchViewsLoadedState = createState<boolean>({
key: 'arePrefetchViewsLoadedState',
defaultValue: false,
});