From 558672810519d3d8c85a949822aeea7caa2fffcb Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 16 Oct 2025 10:30:17 +0200 Subject: [PATCH] Fix infinite loop on new table (#15111) https://github.com/user-attachments/assets/7002cd6c-08d9-4ad7-833f-21bc23d5eea2 --- .../useHandleFindManyRecordsCompleted.ts | 50 ++++++++----------- .../hooks/useLazyFindManyRecords.ts | 34 ++++++++++--- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsCompleted.ts b/packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsCompleted.ts index 6b1da5d8863..0028e5816fe 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsCompleted.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useHandleFindManyRecordsCompleted.ts @@ -1,12 +1,10 @@ -import { useSetRecoilState } from 'recoil'; - import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection'; import { type RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult'; import { cursorFamilyState } from '@/object-record/states/cursorFamilyState'; import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamilyState'; import { type OnFindManyRecordsCompleted } from '@/object-record/types/OnFindManyRecordsCompleted'; -import { useCallback } from 'react'; +import { useRecoilCallback } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; export const useHandleFindManyRecordsCompleted = ({ @@ -18,35 +16,29 @@ export const useHandleFindManyRecordsCompleted = ({ objectMetadataItem: ObjectMetadataItem; onCompleted?: OnFindManyRecordsCompleted; }) => { - const setLastCursor = useSetRecoilState(cursorFamilyState(queryIdentifier)); + const handleFindManyRecordsCompleted = useRecoilCallback( + ({ set }) => + (data: RecordGqlOperationFindManyResult) => { + const pageInfo = data?.[objectMetadataItem.namePlural]?.pageInfo; - const setHasNextPage = useSetRecoilState( - hasNextPageFamilyState(queryIdentifier), - ); + const records = getRecordsFromRecordConnection({ + recordConnection: data?.[objectMetadataItem.namePlural], + }) as T[]; - const handleFindManyRecordsCompleted = useCallback( - (data: RecordGqlOperationFindManyResult) => { - if (!isDefined(data)) { - onCompleted?.([]); - } + onCompleted?.(records, { + pageInfo, + totalCount: data?.[objectMetadataItem.namePlural]?.totalCount, + }); - const pageInfo = data?.[objectMetadataItem.namePlural]?.pageInfo; - - const records = getRecordsFromRecordConnection({ - recordConnection: data?.[objectMetadataItem.namePlural], - }) as T[]; - - onCompleted?.(records, { - pageInfo, - totalCount: data?.[objectMetadataItem.namePlural]?.totalCount, - }); - - if (isDefined(data?.[objectMetadataItem.namePlural])) { - setLastCursor(pageInfo.endCursor ?? ''); - setHasNextPage(pageInfo.hasNextPage ?? false); - } - }, - [onCompleted, objectMetadataItem.namePlural, setLastCursor, setHasNextPage], + if (isDefined(data?.[objectMetadataItem.namePlural])) { + set(cursorFamilyState(queryIdentifier), pageInfo.endCursor ?? ''); + set( + hasNextPageFamilyState(queryIdentifier), + pageInfo.hasNextPage ?? false, + ); + } + }, + [objectMetadataItem.namePlural, onCompleted, queryIdentifier], ); return { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts index f9762663b74..116accf9ae3 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useLazyFindManyRecords.ts @@ -7,7 +7,6 @@ import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getR import { type RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult'; import { type UseFindManyRecordsParams } from '@/object-record/hooks/useFindManyRecords'; import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery'; -import { useHandleFindManyRecordsCompleted } from '@/object-record/hooks/useHandleFindManyRecordsCompleted'; import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError'; import { useLazyFetchMoreRecordsWithPagination } from '@/object-record/hooks/useLazyFetchMoreRecordsWithPagination'; import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject'; @@ -16,6 +15,7 @@ import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamily import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier'; import { QUERY_DEFAULT_LIMIT_RECORDS } from 'twenty-shared/constants'; +import { isDefined } from 'twenty-shared/utils'; type UseLazyFindManyRecordsParams = Omit< UseFindManyRecordsParams, @@ -52,17 +52,39 @@ export const useLazyFindManyRecords = ({ limit, }); - const { handleFindManyRecordsCompleted } = useHandleFindManyRecordsCompleted({ - objectMetadataItem, - queryIdentifier, - }); - const objectPermissions = useObjectPermissionsForObject( objectMetadataItem.id, ); const hasReadPermission = objectPermissions.canReadObjectRecords; + const handleFindManyRecordsCompleted = useRecoilCallback( + ({ set, snapshot }) => + (data: RecordGqlOperationFindManyResult) => { + const pageInfo = data?.[objectMetadataItem.namePlural]?.pageInfo; + + const existingCursor = snapshot + .getLoadable(cursorFamilyState(queryIdentifier)) + .getValue(); + const existingHasNextPage = snapshot + .getLoadable(hasNextPageFamilyState(queryIdentifier)) + .getValue(); + + if (isDefined(data?.[objectMetadataItem.namePlural])) { + if (existingCursor !== pageInfo.endCursor) { + set(cursorFamilyState(queryIdentifier), pageInfo.endCursor ?? ''); + } + if (existingHasNextPage !== pageInfo.hasNextPage) { + set( + hasNextPageFamilyState(queryIdentifier), + pageInfo.hasNextPage ?? false, + ); + } + } + }, + [objectMetadataItem.namePlural, queryIdentifier], + ); + const [findManyRecords, { data, error, fetchMore }] = useLazyQuery(findManyRecordsQuery, { variables: {