☑️ Refacto "Select All/Unselect all" on indexes (#5320)
### Description - Refacto "Select All/Unselect all" on indexes - Add sequential mass deletion from front end (limited to 10k records) - Fixed coverage with new unit tests on new useFetchAllRecordIds hook and other utils ### Refs Closes #4397 Closes #5169 ### Demo https://github.com/twentyhq/twenty/assets/26528466/2658ad2c-827e-4670-b42b-3092e268ff32 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: Toledodev <rafael.toledo@engenharia.ufjf.br> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -0,0 +1,81 @@
|
||||
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
import { peopleQueryResult } from '~/testing/mock-data/people';
|
||||
|
||||
|
||||
export const query = gql`
|
||||
query FindManyPeople($filter: PersonFilterInput, $orderBy: [PersonOrderByInput], $lastCursor: String, $limit: Int) {
|
||||
people(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
id
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const mockPageSize = 2;
|
||||
|
||||
export const peopleMockWithIdsOnly: RecordGqlConnection = { ...peopleQueryResult.people,edges: peopleQueryResult.people.edges.map((edge) => ({ ...edge, node: { __typename: 'Person', id: edge.node.id } })) };
|
||||
|
||||
export const firstRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize].cursor;
|
||||
export const secondRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 2].cursor;
|
||||
export const thirdRequestLastCursor = peopleMockWithIdsOnly.edges[mockPageSize * 3].cursor;
|
||||
|
||||
export const variablesFirstRequest = {
|
||||
filter: undefined,
|
||||
limit: undefined,
|
||||
orderBy: undefined
|
||||
};
|
||||
|
||||
export const variablesSecondRequest = {
|
||||
filter: undefined,
|
||||
limit: undefined,
|
||||
orderBy: undefined,
|
||||
lastCursor: firstRequestLastCursor
|
||||
};
|
||||
|
||||
export const variablesThirdRequest = {
|
||||
filter: undefined,
|
||||
limit: undefined,
|
||||
orderBy: undefined,
|
||||
lastCursor: secondRequestLastCursor
|
||||
}
|
||||
|
||||
const paginateRequestResponse = (response: RecordGqlConnection, start: number, end: number, hasNextPage: boolean, totalCount: number) => {
|
||||
return {
|
||||
...response,
|
||||
edges: [
|
||||
...response.edges.slice(start, end)
|
||||
],
|
||||
pageInfo: {
|
||||
...response.pageInfo,
|
||||
startCursor: response.edges[start].cursor,
|
||||
endCursor: response.edges[end].cursor,
|
||||
hasNextPage,
|
||||
} satisfies RecordGqlConnection['pageInfo'],
|
||||
totalCount,
|
||||
}
|
||||
}
|
||||
|
||||
export const responseFirstRequest = {
|
||||
people: paginateRequestResponse(peopleMockWithIdsOnly, 0, mockPageSize, true, 6),
|
||||
};
|
||||
|
||||
export const responseSecondRequest = {
|
||||
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize, mockPageSize * 2, true, 6),
|
||||
};
|
||||
|
||||
export const responseThirdRequest = {
|
||||
people: paginateRequestResponse(peopleMockWithIdsOnly, mockPageSize * 2, mockPageSize * 3, false, 6),
|
||||
};
|
||||
@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import {
|
||||
@ -23,7 +23,7 @@ const mocks = [
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
deletePeople: responseData,
|
||||
deletePeople: [responseData],
|
||||
},
|
||||
})),
|
||||
},
|
||||
@ -49,7 +49,7 @@ describe('useDeleteManyRecords', () => {
|
||||
await act(async () => {
|
||||
const res = await result.current.deleteManyRecords(people);
|
||||
expect(res).toBeDefined();
|
||||
expect(res).toHaveProperty('id');
|
||||
expect(res[0]).toHaveProperty('id');
|
||||
});
|
||||
|
||||
expect(mocks[0].result).toHaveBeenCalled();
|
||||
|
||||
@ -0,0 +1,107 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { RecoilRoot, useRecoilState } from 'recoil';
|
||||
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock';
|
||||
import {
|
||||
mockPageSize,
|
||||
peopleMockWithIdsOnly,
|
||||
query,
|
||||
responseFirstRequest,
|
||||
responseSecondRequest,
|
||||
responseThirdRequest,
|
||||
variablesFirstRequest,
|
||||
variablesSecondRequest,
|
||||
variablesThirdRequest,
|
||||
} from '@/object-record/hooks/__mocks__/useFetchAllRecordIds';
|
||||
import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds';
|
||||
import { SnackBarManagerScopeInternalContext } from '@/ui/feedback/snack-bar-manager/scopes/scope-internal-context/SnackBarManagerScopeInternalContext';
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
delay: 100,
|
||||
request: {
|
||||
query,
|
||||
variables: variablesFirstRequest,
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: responseFirstRequest,
|
||||
})),
|
||||
},
|
||||
{
|
||||
delay: 100,
|
||||
request: {
|
||||
query,
|
||||
variables: variablesSecondRequest,
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: responseSecondRequest,
|
||||
})),
|
||||
},
|
||||
{
|
||||
delay: 100,
|
||||
request: {
|
||||
query,
|
||||
variables: variablesThirdRequest,
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: responseThirdRequest,
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
describe('useFetchAllRecordIds', () => {
|
||||
it('fetches all record ids with fetch more synchronous loop', async () => {
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<SnackBarManagerScopeInternalContext.Provider
|
||||
value={{
|
||||
scopeId: 'snack-bar-manager',
|
||||
}}
|
||||
>
|
||||
<MockedProvider mocks={mocks} addTypename={false}>
|
||||
{children}
|
||||
</MockedProvider>
|
||||
</SnackBarManagerScopeInternalContext.Provider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const [, setObjectMetadataItems] = useRecoilState(
|
||||
objectMetadataItemsState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setObjectMetadataItems(getObjectMetadataItemsMock());
|
||||
}, [setObjectMetadataItems]);
|
||||
|
||||
return useFetchAllRecordIds({
|
||||
objectNameSingular: 'person',
|
||||
pageSize: mockPageSize,
|
||||
});
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
const { fetchAllRecordIds } = result.current;
|
||||
|
||||
let recordIds: string[] = [];
|
||||
|
||||
await act(async () => {
|
||||
recordIds = await fetchAllRecordIds();
|
||||
});
|
||||
|
||||
expect(mocks[0].result).toHaveBeenCalled();
|
||||
expect(mocks[1].result).toHaveBeenCalled();
|
||||
expect(mocks[2].result).toHaveBeenCalled();
|
||||
|
||||
expect(recordIds).toEqual(
|
||||
peopleMockWithIdsOnly.edges.map((edge) => edge.node.id).slice(0, 6),
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,6 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
|
||||
@ -4,9 +4,11 @@ import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
|
||||
import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation';
|
||||
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
type useDeleteOneRecordProps = {
|
||||
@ -16,6 +18,7 @@ type useDeleteOneRecordProps = {
|
||||
|
||||
type DeleteManyRecordsOptions = {
|
||||
skipOptimisticEffect?: boolean;
|
||||
delayInMsBetweenRequests?: number;
|
||||
};
|
||||
|
||||
export const useDeleteManyRecords = ({
|
||||
@ -45,40 +48,62 @@ export const useDeleteManyRecords = ({
|
||||
idsToDelete: string[],
|
||||
options?: DeleteManyRecordsOptions,
|
||||
) => {
|
||||
const deletedRecords = await apolloClient.mutate({
|
||||
mutation: deleteManyRecordsMutation,
|
||||
variables: {
|
||||
filter: { id: { in: idsToDelete } },
|
||||
},
|
||||
optimisticResponse: options?.skipOptimisticEffect
|
||||
? undefined
|
||||
: {
|
||||
[mutationResponseField]: idsToDelete.map((idToDelete) => ({
|
||||
__typename: capitalize(objectNameSingular),
|
||||
id: idToDelete,
|
||||
})),
|
||||
},
|
||||
update: options?.skipOptimisticEffect
|
||||
? undefined
|
||||
: (cache, { data }) => {
|
||||
const records = data?.[mutationResponseField];
|
||||
const numberOfBatches = Math.ceil(
|
||||
idsToDelete.length / DEFAULT_MUTATION_BATCH_SIZE,
|
||||
);
|
||||
|
||||
if (!records?.length) return;
|
||||
const deletedRecords = [];
|
||||
|
||||
const cachedRecords = records
|
||||
.map((record) => getRecordFromCache(record.id, cache))
|
||||
.filter(isDefined);
|
||||
for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) {
|
||||
const batchIds = idsToDelete.slice(
|
||||
batchIndex * DEFAULT_MUTATION_BATCH_SIZE,
|
||||
(batchIndex + 1) * DEFAULT_MUTATION_BATCH_SIZE,
|
||||
);
|
||||
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
recordsToDelete: cachedRecords,
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
});
|
||||
const deletedRecordsResponse = await apolloClient.mutate({
|
||||
mutation: deleteManyRecordsMutation,
|
||||
variables: {
|
||||
filter: { id: { in: batchIds } },
|
||||
},
|
||||
optimisticResponse: options?.skipOptimisticEffect
|
||||
? undefined
|
||||
: {
|
||||
[mutationResponseField]: batchIds.map((idToDelete) => ({
|
||||
__typename: capitalize(objectNameSingular),
|
||||
id: idToDelete,
|
||||
})),
|
||||
},
|
||||
update: options?.skipOptimisticEffect
|
||||
? undefined
|
||||
: (cache, { data }) => {
|
||||
const records = data?.[mutationResponseField];
|
||||
|
||||
return deletedRecords.data?.[mutationResponseField] ?? null;
|
||||
if (!records?.length) return;
|
||||
|
||||
const cachedRecords = records
|
||||
.map((record) => getRecordFromCache(record.id, cache))
|
||||
.filter(isDefined);
|
||||
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
recordsToDelete: cachedRecords,
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const deletedRecordsForThisBatch =
|
||||
deletedRecordsResponse.data?.[mutationResponseField] ?? [];
|
||||
|
||||
deletedRecords.push(...deletedRecordsForThisBatch);
|
||||
|
||||
if (isDefined(options?.delayInMsBetweenRequests)) {
|
||||
await sleep(options.delayInMsBetweenRequests);
|
||||
}
|
||||
}
|
||||
|
||||
return deletedRecords;
|
||||
};
|
||||
|
||||
return { deleteManyRecords };
|
||||
|
||||
@ -0,0 +1,78 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryPageSize';
|
||||
import { UseFindManyRecordsParams } from '@/object-record/hooks/useFetchMoreRecordsWithPagination';
|
||||
import { useLazyFindManyRecords } from '@/object-record/hooks/useLazyFindManyRecords';
|
||||
import { useCallback } from 'react';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
type UseLazyFetchAllRecordIdsParams<T> = Omit<
|
||||
UseFindManyRecordsParams<T>,
|
||||
'skip'
|
||||
> & { pageSize?: number };
|
||||
|
||||
export const useFetchAllRecordIds = <T>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
pageSize = DEFAULT_QUERY_PAGE_SIZE,
|
||||
}: UseLazyFetchAllRecordIdsParams<T>) => {
|
||||
const { fetchMore, findManyRecords } = useLazyFindManyRecords({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
recordGqlFields: { id: true },
|
||||
});
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const fetchAllRecordIds = useCallback(async () => {
|
||||
if (!isDefined(findManyRecords)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const findManyRecordsDataResult = await findManyRecords();
|
||||
|
||||
const firstQueryResult =
|
||||
findManyRecordsDataResult?.data?.[objectMetadataItem.namePlural];
|
||||
|
||||
const totalCount = firstQueryResult?.totalCount ?? 1;
|
||||
|
||||
const recordsCount = firstQueryResult?.edges.length ?? 0;
|
||||
|
||||
const recordIdSet = new Set(
|
||||
firstQueryResult?.edges?.map((edge) => edge.node.id) ?? [],
|
||||
);
|
||||
|
||||
const remainingCount = totalCount - recordsCount;
|
||||
|
||||
const remainingPages = Math.ceil(remainingCount / pageSize);
|
||||
|
||||
let lastCursor = firstQueryResult?.pageInfo.endCursor ?? '';
|
||||
|
||||
for (let i = 0; i < remainingPages; i++) {
|
||||
const rawResult = await fetchMore?.({
|
||||
variables: {
|
||||
lastCursor: lastCursor,
|
||||
},
|
||||
});
|
||||
|
||||
const fetchMoreResult = rawResult?.data?.[objectMetadataItem.namePlural];
|
||||
|
||||
for (const edge of fetchMoreResult.edges) {
|
||||
recordIdSet.add(edge.node.id);
|
||||
}
|
||||
|
||||
lastCursor = fetchMoreResult.pageInfo.endCursor ?? '';
|
||||
}
|
||||
|
||||
const recordIds = Array.from(recordIdSet);
|
||||
|
||||
return recordIds;
|
||||
}, [fetchMore, findManyRecords, objectMetadataItem.namePlural, pageSize]);
|
||||
|
||||
return {
|
||||
fetchAllRecordIds,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,229 @@
|
||||
import {
|
||||
ApolloError,
|
||||
ApolloQueryResult,
|
||||
FetchMoreQueryOptions,
|
||||
OperationVariables,
|
||||
WatchQueryFetchPolicy,
|
||||
} from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useMemo } from 'react';
|
||||
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { isAggregationEnabled } from '@/object-metadata/utils/isAggregationEnabled';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { RecordGqlEdge } from '@/object-record/graphql/types/RecordGqlEdge';
|
||||
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
||||
import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { OnFindManyRecordsCompleted } from '@/object-record/types/OnFindManyRecordsCompleted';
|
||||
import { filterUniqueRecordEdgesByCursor } from '@/object-record/utils/filterUniqueRecordEdgesByCursor';
|
||||
import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
import { cursorFamilyState } from '../states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '../states/hasNextPageFamilyState';
|
||||
import { isFetchingMoreRecordsFamilyState } from '../states/isFetchingMoreRecordsFamilyState';
|
||||
|
||||
export type UseFindManyRecordsParams<T> = ObjectMetadataItemIdentifier &
|
||||
RecordGqlOperationVariables & {
|
||||
onCompleted?: OnFindManyRecordsCompleted<T>;
|
||||
skip?: boolean;
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
fetchPolicy?: WatchQueryFetchPolicy;
|
||||
};
|
||||
|
||||
type UseFindManyRecordsStateParams<
|
||||
T,
|
||||
TData = RecordGqlOperationFindManyResult,
|
||||
> = Omit<
|
||||
UseFindManyRecordsParams<T>,
|
||||
'skip' | 'recordGqlFields' | 'fetchPolicy'
|
||||
> & {
|
||||
data: RecordGqlOperationFindManyResult | undefined;
|
||||
error: ApolloError | undefined;
|
||||
fetchMore<
|
||||
TFetchData = TData,
|
||||
TFetchVars extends OperationVariables = OperationVariables,
|
||||
>(
|
||||
fetchMoreOptions: FetchMoreQueryOptions<TFetchVars, TFetchData> & {
|
||||
updateQuery?: (
|
||||
previousQueryResult: TData,
|
||||
options: {
|
||||
fetchMoreResult: TFetchData;
|
||||
variables: TFetchVars;
|
||||
},
|
||||
) => TData;
|
||||
},
|
||||
): Promise<ApolloQueryResult<TFetchData>>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const useFetchMoreRecordsWithPagination = <
|
||||
T extends ObjectRecord = ObjectRecord,
|
||||
>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit,
|
||||
data,
|
||||
error,
|
||||
fetchMore,
|
||||
objectMetadataItem,
|
||||
onCompleted,
|
||||
}: UseFindManyRecordsStateParams<T>) => {
|
||||
const queryIdentifier = getQueryIdentifier({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
limit,
|
||||
orderBy,
|
||||
});
|
||||
|
||||
const [hasNextPage] = useRecoilState(hasNextPageFamilyState(queryIdentifier));
|
||||
|
||||
const setIsFetchingMoreObjects = useSetRecoilState(
|
||||
isFetchingMoreRecordsFamilyState(queryIdentifier),
|
||||
);
|
||||
|
||||
const { handleFindManyRecordsError } = useHandleFindManyRecordsError({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
// TODO: put this into a util inspired from https://github.com/apollographql/apollo-client/blob/master/src/utilities/policies/pagination.ts
|
||||
// This function is equivalent to merge function + read function in field policy
|
||||
const fetchMoreRecords = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async () => {
|
||||
const hasNextPageLocal = snapshot
|
||||
.getLoadable(hasNextPageFamilyState(queryIdentifier))
|
||||
.getValue();
|
||||
|
||||
const lastCursorLocal = snapshot
|
||||
.getLoadable(cursorFamilyState(queryIdentifier))
|
||||
.getValue();
|
||||
|
||||
// Remote objects does not support hasNextPage. We cannot rely on it to fetch more records.
|
||||
if (
|
||||
hasNextPageLocal ||
|
||||
(!isAggregationEnabled(objectMetadataItem) && !error)
|
||||
) {
|
||||
setIsFetchingMoreObjects(true);
|
||||
|
||||
try {
|
||||
const { data: fetchMoreDataResult } = await fetchMore({
|
||||
variables: {
|
||||
filter,
|
||||
orderBy,
|
||||
lastCursor: isNonEmptyString(lastCursorLocal)
|
||||
? lastCursorLocal
|
||||
: undefined,
|
||||
},
|
||||
updateQuery: (prev, { fetchMoreResult }) => {
|
||||
const previousEdges =
|
||||
prev?.[objectMetadataItem.namePlural]?.edges;
|
||||
const nextEdges =
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.edges;
|
||||
|
||||
let newEdges: RecordGqlEdge[] = previousEdges ?? [];
|
||||
|
||||
if (isNonEmptyArray(nextEdges)) {
|
||||
newEdges = filterUniqueRecordEdgesByCursor([
|
||||
...newEdges,
|
||||
...(fetchMoreResult?.[objectMetadataItem.namePlural]
|
||||
?.edges ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
const pageInfo =
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo;
|
||||
|
||||
if (isDefined(data?.[objectMetadataItem.namePlural])) {
|
||||
set(
|
||||
cursorFamilyState(queryIdentifier),
|
||||
pageInfo.endCursor ?? '',
|
||||
);
|
||||
set(
|
||||
hasNextPageFamilyState(queryIdentifier),
|
||||
pageInfo.hasNextPage ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
const records = getRecordsFromRecordConnection({
|
||||
recordConnection: {
|
||||
edges: newEdges,
|
||||
pageInfo,
|
||||
},
|
||||
}) as T[];
|
||||
|
||||
onCompleted?.(records, {
|
||||
pageInfo,
|
||||
totalCount:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]
|
||||
?.totalCount,
|
||||
});
|
||||
|
||||
return Object.assign({}, prev, {
|
||||
[objectMetadataItem.namePlural]: {
|
||||
__typename: `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Connection`,
|
||||
edges: newEdges,
|
||||
pageInfo:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural].pageInfo,
|
||||
totalCount:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]
|
||||
.totalCount,
|
||||
},
|
||||
} as RecordGqlOperationFindManyResult);
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
data: fetchMoreDataResult?.[objectMetadataItem.namePlural],
|
||||
};
|
||||
} catch (error) {
|
||||
handleFindManyRecordsError(error as ApolloError);
|
||||
} finally {
|
||||
setIsFetchingMoreObjects(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
objectMetadataItem,
|
||||
error,
|
||||
setIsFetchingMoreObjects,
|
||||
fetchMore,
|
||||
filter,
|
||||
orderBy,
|
||||
data,
|
||||
onCompleted,
|
||||
handleFindManyRecordsError,
|
||||
queryIdentifier,
|
||||
],
|
||||
);
|
||||
|
||||
const totalCount = data?.[objectMetadataItem.namePlural]?.totalCount;
|
||||
|
||||
const records = useMemo(
|
||||
() =>
|
||||
data?.[objectMetadataItem.namePlural]
|
||||
? getRecordsFromRecordConnection<T>({
|
||||
recordConnection: data?.[objectMetadataItem.namePlural],
|
||||
})
|
||||
: ([] as T[]),
|
||||
|
||||
[data, objectMetadataItem.namePlural],
|
||||
);
|
||||
|
||||
return {
|
||||
fetchMoreRecords,
|
||||
totalCount,
|
||||
records,
|
||||
hasNextPage,
|
||||
};
|
||||
};
|
||||
@ -1,85 +1,66 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQuery, WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
import { isAggregationEnabled } from '@/object-metadata/utils/isAggregationEnabled';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { RecordGqlConnection } from '@/object-record/graphql/types/RecordGqlConnection';
|
||||
import { RecordGqlEdge } from '@/object-record/graphql/types/RecordGqlEdge';
|
||||
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
||||
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
import { RecordGqlOperationVariables } from '@/object-record/graphql/types/RecordGqlOperationVariables';
|
||||
import { useFetchMoreRecordsWithPagination } from '@/object-record/hooks/useFetchMoreRecordsWithPagination';
|
||||
import { useFindManyRecordsQuery } from '@/object-record/hooks/useFindManyRecordsQuery';
|
||||
import { useHandleFindManyRecordsCompleted } from '@/object-record/hooks/useHandleFindManyRecordsCompleted';
|
||||
import { useHandleFindManyRecordsError } from '@/object-record/hooks/useHandleFindManyRecordsError';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { filterUniqueRecordEdgesByCursor } from '@/object-record/utils/filterUniqueRecordEdgesByCursor';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { logError } from '~/utils/logError';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
import { OnFindManyRecordsCompleted } from '@/object-record/types/OnFindManyRecordsCompleted';
|
||||
import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier';
|
||||
|
||||
import { cursorFamilyState } from '../states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '../states/hasNextPageFamilyState';
|
||||
import { isFetchingMoreRecordsFamilyState } from '../states/isFetchingMoreRecordsFamilyState';
|
||||
export type UseFindManyRecordsParams<T> = ObjectMetadataItemIdentifier &
|
||||
RecordGqlOperationVariables & {
|
||||
onError?: (error?: Error) => void;
|
||||
onCompleted?: OnFindManyRecordsCompleted<T>;
|
||||
skip?: boolean;
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
fetchPolicy?: WatchQueryFetchPolicy;
|
||||
};
|
||||
|
||||
export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit,
|
||||
onCompleted,
|
||||
onError,
|
||||
skip,
|
||||
recordGqlFields,
|
||||
fetchPolicy,
|
||||
}: ObjectMetadataItemIdentifier &
|
||||
RecordGqlOperationVariables & {
|
||||
onCompleted?: (
|
||||
records: T[],
|
||||
options?: {
|
||||
pageInfo?: RecordGqlConnection['pageInfo'];
|
||||
totalCount?: number;
|
||||
},
|
||||
) => void;
|
||||
onError?: (error?: Error) => void;
|
||||
skip?: boolean;
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
fetchPolicy?: WatchQueryFetchPolicy;
|
||||
}) => {
|
||||
const findManyQueryStateIdentifier =
|
||||
objectNameSingular +
|
||||
JSON.stringify(filter) +
|
||||
JSON.stringify(orderBy) +
|
||||
limit;
|
||||
|
||||
const [lastCursor, setLastCursor] = useRecoilState(
|
||||
cursorFamilyState(findManyQueryStateIdentifier),
|
||||
);
|
||||
|
||||
const [hasNextPage, setHasNextPage] = useRecoilState(
|
||||
hasNextPageFamilyState(findManyQueryStateIdentifier),
|
||||
);
|
||||
|
||||
const setIsFetchingMoreObjects = useSetRecoilState(
|
||||
isFetchingMoreRecordsFamilyState(findManyQueryStateIdentifier),
|
||||
);
|
||||
|
||||
onError,
|
||||
onCompleted,
|
||||
}: UseFindManyRecordsParams<T>) => {
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { findManyRecordsQuery } = useFindManyRecordsQuery({
|
||||
objectNameSingular,
|
||||
recordGqlFields,
|
||||
});
|
||||
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const { handleFindManyRecordsError } = useHandleFindManyRecordsError({
|
||||
objectMetadataItem,
|
||||
handleError: onError,
|
||||
});
|
||||
|
||||
const queryIdentifier = getQueryIdentifier({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit,
|
||||
});
|
||||
|
||||
const { handleFindManyRecordsCompleted } = useHandleFindManyRecordsCompleted({
|
||||
objectMetadataItem,
|
||||
queryIdentifier,
|
||||
onCompleted,
|
||||
});
|
||||
|
||||
const { data, loading, error, fetchMore } =
|
||||
useQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
||||
@ -90,147 +71,21 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
orderBy,
|
||||
},
|
||||
fetchPolicy: fetchPolicy,
|
||||
onCompleted: (data) => {
|
||||
if (!isDefined(data)) {
|
||||
onCompleted?.([]);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
logError(
|
||||
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueSnackBar(
|
||||
`Error during useFindManyRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
|
||||
{
|
||||
variant: SnackBarVariant.Error,
|
||||
},
|
||||
);
|
||||
onError?.(error);
|
||||
},
|
||||
onCompleted: handleFindManyRecordsCompleted,
|
||||
onError: handleFindManyRecordsError,
|
||||
});
|
||||
|
||||
const fetchMoreRecords = useCallback(async () => {
|
||||
// Remote objects does not support hasNextPage. We cannot rely on it to fetch more records.
|
||||
if (hasNextPage || (!isAggregationEnabled(objectMetadataItem) && !error)) {
|
||||
setIsFetchingMoreObjects(true);
|
||||
|
||||
try {
|
||||
await fetchMore({
|
||||
variables: {
|
||||
filter,
|
||||
orderBy,
|
||||
lastCursor: isNonEmptyString(lastCursor) ? lastCursor : undefined,
|
||||
},
|
||||
updateQuery: (prev, { fetchMoreResult }) => {
|
||||
const previousEdges = prev?.[objectMetadataItem.namePlural]?.edges;
|
||||
const nextEdges =
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.edges;
|
||||
|
||||
let newEdges: RecordGqlEdge[] = previousEdges ?? [];
|
||||
|
||||
if (isNonEmptyArray(nextEdges)) {
|
||||
newEdges = filterUniqueRecordEdgesByCursor([
|
||||
...newEdges,
|
||||
...(fetchMoreResult?.[objectMetadataItem.namePlural]?.edges ??
|
||||
[]),
|
||||
]);
|
||||
}
|
||||
|
||||
const pageInfo =
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.pageInfo;
|
||||
|
||||
if (isDefined(data?.[objectMetadataItem.namePlural])) {
|
||||
setLastCursor(pageInfo.endCursor ?? '');
|
||||
setHasNextPage(pageInfo.hasNextPage ?? false);
|
||||
}
|
||||
|
||||
const records = getRecordsFromRecordConnection({
|
||||
recordConnection: {
|
||||
edges: newEdges,
|
||||
pageInfo,
|
||||
},
|
||||
}) as T[];
|
||||
|
||||
onCompleted?.(records, {
|
||||
pageInfo,
|
||||
totalCount:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural]?.totalCount,
|
||||
});
|
||||
|
||||
return Object.assign({}, prev, {
|
||||
[objectMetadataItem.namePlural]: {
|
||||
__typename: `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Connection`,
|
||||
edges: newEdges,
|
||||
pageInfo:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural].pageInfo,
|
||||
totalCount:
|
||||
fetchMoreResult?.[objectMetadataItem.namePlural].totalCount,
|
||||
},
|
||||
} as RecordGqlOperationFindManyResult);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logError(
|
||||
`fetchMoreObjects for "${objectMetadataItem.namePlural}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueSnackBar(
|
||||
`Error during fetchMoreObjects for "${objectMetadataItem.namePlural}", ${error}`,
|
||||
{
|
||||
variant: SnackBarVariant.Error,
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
setIsFetchingMoreObjects(false);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
hasNextPage,
|
||||
objectMetadataItem,
|
||||
error,
|
||||
setIsFetchingMoreObjects,
|
||||
fetchMore,
|
||||
filter,
|
||||
orderBy,
|
||||
lastCursor,
|
||||
data,
|
||||
onCompleted,
|
||||
setLastCursor,
|
||||
setHasNextPage,
|
||||
enqueueSnackBar,
|
||||
]);
|
||||
|
||||
const totalCount = data?.[objectMetadataItem.namePlural]?.totalCount;
|
||||
|
||||
const records = useMemo(
|
||||
() =>
|
||||
data?.[objectMetadataItem.namePlural]
|
||||
? getRecordsFromRecordConnection<T>({
|
||||
recordConnection: data?.[objectMetadataItem.namePlural],
|
||||
})
|
||||
: ([] as T[]),
|
||||
|
||||
[data, objectMetadataItem.namePlural],
|
||||
);
|
||||
const { fetchMoreRecords, totalCount, records, hasNextPage } =
|
||||
useFetchMoreRecordsWithPagination<T>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit,
|
||||
fetchMore,
|
||||
data,
|
||||
error,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
@ -239,7 +94,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
loading,
|
||||
error,
|
||||
fetchMoreRecords,
|
||||
queryStateIdentifier: findManyQueryStateIdentifier,
|
||||
queryStateIdentifier: queryIdentifier,
|
||||
hasNextPage,
|
||||
};
|
||||
};
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
import { useRecoilState } from 'recoil';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getRecordsFromRecordConnection } from '@/object-record/cache/utils/getRecordsFromRecordConnection';
|
||||
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
||||
import { cursorFamilyState } from '@/object-record/states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamilyState';
|
||||
import { OnFindManyRecordsCompleted } from '@/object-record/types/OnFindManyRecordsCompleted';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const useHandleFindManyRecordsCompleted = <T>({
|
||||
queryIdentifier,
|
||||
onCompleted,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
queryIdentifier: string;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
onCompleted?: OnFindManyRecordsCompleted<T>;
|
||||
}) => {
|
||||
const [, setLastCursor] = useRecoilState(cursorFamilyState(queryIdentifier));
|
||||
|
||||
const [, setHasNextPage] = useRecoilState(
|
||||
hasNextPageFamilyState(queryIdentifier),
|
||||
);
|
||||
|
||||
const handleFindManyRecordsCompleted = (
|
||||
data: RecordGqlOperationFindManyResult,
|
||||
) => {
|
||||
if (!isDefined(data)) {
|
||||
onCompleted?.([]);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handleFindManyRecordsCompleted,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
import { ApolloError } from '@apollo/client';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
export const useHandleFindManyRecordsError = ({
|
||||
handleError,
|
||||
objectMetadataItem,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
handleError?: (error?: Error) => void;
|
||||
}) => {
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const handleFindManyRecordsError = (error: ApolloError) => {
|
||||
logError(
|
||||
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueSnackBar(
|
||||
`Error during useFindManyRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
|
||||
{
|
||||
variant: SnackBarVariant.Error,
|
||||
},
|
||||
);
|
||||
handleError?.(error);
|
||||
};
|
||||
|
||||
return {
|
||||
handleFindManyRecordsError,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,115 @@
|
||||
import { useLazyQuery } from '@apollo/client';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { RecordGqlOperationFindManyResult } from '@/object-record/graphql/types/RecordGqlOperationFindManyResult';
|
||||
import { useFetchMoreRecordsWithPagination } from '@/object-record/hooks/useFetchMoreRecordsWithPagination';
|
||||
import { 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 { cursorFamilyState } from '@/object-record/states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamilyState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getQueryIdentifier } from '@/object-record/utils/getQueryIdentifier';
|
||||
|
||||
type UseLazyFindManyRecordsParams<T> = Omit<
|
||||
UseFindManyRecordsParams<T>,
|
||||
'skip'
|
||||
>;
|
||||
|
||||
export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit,
|
||||
recordGqlFields,
|
||||
fetchPolicy,
|
||||
onCompleted,
|
||||
onError,
|
||||
}: UseLazyFindManyRecordsParams<T>) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { findManyRecordsQuery } = useFindManyRecordsQuery({
|
||||
objectNameSingular,
|
||||
recordGqlFields,
|
||||
});
|
||||
|
||||
const { handleFindManyRecordsError } = useHandleFindManyRecordsError({
|
||||
objectMetadataItem,
|
||||
handleError: onError,
|
||||
});
|
||||
|
||||
const queryIdentifier = getQueryIdentifier({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit,
|
||||
});
|
||||
|
||||
const { handleFindManyRecordsCompleted } = useHandleFindManyRecordsCompleted({
|
||||
objectMetadataItem,
|
||||
queryIdentifier,
|
||||
onCompleted,
|
||||
});
|
||||
|
||||
const [findManyRecords, { data, loading, error, fetchMore }] =
|
||||
useLazyQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
||||
variables: {
|
||||
filter,
|
||||
limit,
|
||||
orderBy,
|
||||
},
|
||||
fetchPolicy: fetchPolicy,
|
||||
onCompleted: handleFindManyRecordsCompleted,
|
||||
onError: handleFindManyRecordsError,
|
||||
});
|
||||
|
||||
const { fetchMoreRecords, totalCount, records } =
|
||||
useFetchMoreRecordsWithPagination<T>({
|
||||
objectNameSingular,
|
||||
filter,
|
||||
orderBy,
|
||||
limit,
|
||||
onCompleted,
|
||||
fetchMore,
|
||||
data,
|
||||
error,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const findManyRecordsLazy = useRecoilCallback(
|
||||
({ set }) =>
|
||||
async () => {
|
||||
const result = await findManyRecords();
|
||||
|
||||
const hasNextPage =
|
||||
result?.data?.[objectMetadataItem.namePlural]?.pageInfo.hasNextPage ??
|
||||
false;
|
||||
|
||||
const lastCursor =
|
||||
result?.data?.[objectMetadataItem.namePlural]?.pageInfo.endCursor ??
|
||||
'';
|
||||
|
||||
set(hasNextPageFamilyState(queryIdentifier), hasNextPage);
|
||||
set(cursorFamilyState(queryIdentifier), lastCursor);
|
||||
|
||||
return result;
|
||||
},
|
||||
[queryIdentifier, findManyRecords, objectMetadataItem],
|
||||
);
|
||||
|
||||
return {
|
||||
objectMetadataItem,
|
||||
records,
|
||||
totalCount,
|
||||
loading,
|
||||
error,
|
||||
fetchMore,
|
||||
fetchMoreRecordsWithPagination: fetchMoreRecords,
|
||||
queryStateIdentifier: queryIdentifier,
|
||||
findManyRecords: findManyRecordsLazy,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user