Revert optimistic rendering on negative response (#7541)

Fixes #7299

The changes primarily focus on ensuring that records are correctly
handled in the cache and optimistic effects are reverted appropriately
when mutations fail.
This commit is contained in:
Félix Malfait
2024-10-09 16:18:55 +02:00
committed by GitHub
parent f901512a4f
commit be49d4fe5d
8 changed files with 351 additions and 166 deletions

View File

@ -2,9 +2,11 @@ import { useApolloClient } from '@apollo/client';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache'; import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache';
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields'; import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
@ -67,7 +69,7 @@ export const useCreateManyRecords = <
}, },
); );
const recordsCreatedInCache = []; const recordsCreatedInCache: ObjectRecord[] = [];
for (const recordToCreate of sanitizedCreateManyRecordsInput) { for (const recordToCreate of sanitizedCreateManyRecordsInput) {
if (recordToCreate.id === null) { if (recordToCreate.id === null) {
@ -98,26 +100,46 @@ export const useCreateManyRecords = <
objectMetadataItem.namePlural, objectMetadataItem.namePlural,
); );
const createdObjects = await apolloClient.mutate({ const createdObjects = await apolloClient
mutation: createManyRecordsMutation, .mutate({
variables: { mutation: createManyRecordsMutation,
data: sanitizedCreateManyRecordsInput, variables: {
upsert: upsert, data: sanitizedCreateManyRecordsInput,
}, upsert: upsert,
update: (cache, { data }) => { },
const records = data?.[mutationResponseField]; update: (cache, { data }) => {
const records = data?.[mutationResponseField];
if (!records?.length || skipPostOptmisticEffect) return; if (!records?.length || skipPostOptmisticEffect) return;
triggerCreateRecordsOptimisticEffect({ triggerCreateRecordsOptimisticEffect({
cache, cache,
objectMetadataItem, objectMetadataItem,
recordsToCreate: records, recordsToCreate: records,
objectMetadataItems, objectMetadataItems,
shouldMatchRootQueryFilter, shouldMatchRootQueryFilter,
});
},
})
.catch((error: Error) => {
recordsCreatedInCache.forEach((recordToDelete) => {
deleteRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
recordToDelete,
});
}); });
},
}); triggerDeleteRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
recordsToDelete: recordsCreatedInCache,
objectMetadataItems,
});
throw error;
});
return createdObjects.data?.[mutationResponseField] ?? []; return createdObjects.data?.[mutationResponseField] ?? [];
}; };

View File

@ -3,9 +3,11 @@ import { useState } from 'react';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache'; import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache';
import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordFromCache';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields'; import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
@ -85,27 +87,49 @@ export const useCreateOneRecord = <
const mutationResponseField = const mutationResponseField =
getCreateOneRecordMutationResponseField(objectNameSingular); getCreateOneRecordMutationResponseField(objectNameSingular);
const createdObject = await apolloClient.mutate({ const createdObject = await apolloClient
mutation: createOneRecordMutation, .mutate({
variables: { mutation: createOneRecordMutation,
input: sanitizedInput, variables: {
}, input: sanitizedInput,
update: (cache, { data }) => { },
const record = data?.[mutationResponseField]; update: (cache, { data }) => {
const record = data?.[mutationResponseField];
if (!record || skipPostOptmisticEffect) return; if (!record || skipPostOptmisticEffect) return;
triggerCreateRecordsOptimisticEffect({ triggerCreateRecordsOptimisticEffect({
cache, cache,
objectMetadataItem, objectMetadataItem,
recordsToCreate: [record], recordsToCreate: [record],
objectMetadataItems,
shouldMatchRootQueryFilter,
});
setLoading(false);
},
})
.catch((error: Error) => {
if (!recordCreatedInCache) {
throw error;
}
deleteRecordFromCache({
objectMetadataItems, objectMetadataItems,
shouldMatchRootQueryFilter, objectMetadataItem,
cache: apolloClient.cache,
recordToDelete: recordCreatedInCache,
}); });
setLoading(false); triggerDeleteRecordsOptimisticEffect({
}, cache: apolloClient.cache,
}); objectMetadataItem,
recordsToDelete: [recordCreatedInCache],
objectMetadataItems,
});
throw error;
});
return createdObject.data?.[mutationResponseField] ?? null; return createdObject.data?.[mutationResponseField] ?? null;
}; };

View File

@ -1,10 +1,12 @@
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { apiConfigState } from '@/client-config/states/apiConfigState'; import { apiConfigState } from '@/client-config/states/apiConfigState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize'; import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation'; import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation';
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField'; import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField';
@ -65,38 +67,74 @@ export const useDeleteManyRecords = ({
(batchIndex + 1) * mutationPageSize, (batchIndex + 1) * mutationPageSize,
); );
const deletedRecordsResponse = await apolloClient.mutate({ const deletedRecordsResponse = await apolloClient
mutation: deleteManyRecordsMutation, .mutate({
variables: { mutation: deleteManyRecordsMutation,
filter: { id: { in: batchIds } }, variables: {
}, filter: { id: { in: batchIds } },
optimisticResponse: options?.skipOptimisticEffect },
? undefined optimisticResponse: options?.skipOptimisticEffect
: { ? undefined
[mutationResponseField]: batchIds.map((idToDelete) => ({ : {
__typename: capitalize(objectNameSingular), [mutationResponseField]: batchIds.map((idToDelete) => ({
id: idToDelete, __typename: capitalize(objectNameSingular),
id: idToDelete,
})),
},
update: options?.skipOptimisticEffect
? undefined
: (cache, { data }) => {
const records = data?.[mutationResponseField];
if (!records?.length) return;
const cachedRecords = records
.map((record) => getRecordFromCache(record.id, cache))
.filter(isDefined);
triggerDeleteRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToDelete: cachedRecords,
objectMetadataItems,
});
},
})
.catch((error: Error) => {
const cachedRecords = batchIds.map((idToDelete) =>
getRecordFromCache(idToDelete, apolloClient.cache),
);
cachedRecords.forEach((cachedRecord) => {
if (!cachedRecord) {
return;
}
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
record: {
...cachedRecord,
deletedAt: null,
},
});
});
triggerCreateRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
objectMetadataItems,
recordsToCreate: cachedRecords
.filter(isDefined)
.map((cachedRecord) => ({
...cachedRecord,
deletedAt: null,
})), })),
}, });
update: options?.skipOptimisticEffect
? undefined
: (cache, { data }) => {
const records = data?.[mutationResponseField];
if (!records?.length) return; throw error;
});
const cachedRecords = records
.map((record) => getRecordFromCache(record.id, cache))
.filter(isDefined);
triggerDeleteRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToDelete: cachedRecords,
objectMetadataItems,
});
},
});
const deletedRecordsForThisBatch = const deletedRecordsForThisBatch =
deletedRecordsResponse.data?.[mutationResponseField] ?? []; deletedRecordsResponse.data?.[mutationResponseField] ?? [];

View File

@ -1,10 +1,12 @@
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation';
import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
@ -39,35 +41,70 @@ export const useDeleteOneRecord = ({
async (idToDelete: string) => { async (idToDelete: string) => {
const currentTimestamp = new Date().toISOString(); const currentTimestamp = new Date().toISOString();
const deletedRecord = await apolloClient.mutate({ const deletedRecord = await apolloClient
mutation: deleteOneRecordMutation, .mutate({
variables: { mutation: deleteOneRecordMutation,
idToDelete: idToDelete, variables: {
}, idToDelete: idToDelete,
optimisticResponse: {
[mutationResponseField]: {
__typename: capitalize(objectNameSingular),
id: idToDelete,
deletedAt: currentTimestamp,
}, },
}, optimisticResponse: {
update: (cache, { data }) => { [mutationResponseField]: {
const record = data?.[mutationResponseField]; __typename: capitalize(objectNameSingular),
id: idToDelete,
deletedAt: currentTimestamp,
},
},
update: (cache, { data }) => {
const record = data?.[mutationResponseField];
if (!record) return; if (!record) return;
const cachedRecord = getRecordFromCache(record.id, cache); const cachedRecord = getRecordFromCache(record.id, cache);
if (!cachedRecord) return; if (!cachedRecord) return;
triggerDeleteRecordsOptimisticEffect({ triggerDeleteRecordsOptimisticEffect({
cache, cache,
objectMetadataItem, objectMetadataItem,
recordsToDelete: [cachedRecord], recordsToDelete: [cachedRecord],
objectMetadataItems,
});
},
})
.catch((error: Error) => {
const cachedRecord = getRecordFromCache(
idToDelete,
apolloClient.cache,
);
if (!cachedRecord) {
throw error;
}
updateRecordFromCache({
objectMetadataItems, objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
record: {
...cachedRecord,
deletedAt: null,
},
}); });
},
}); triggerCreateRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
objectMetadataItems,
recordsToCreate: [
{
...cachedRecord,
deletedAt: null,
},
],
});
throw error;
});
return deletedRecord.data?.[mutationResponseField] ?? null; return deletedRecord.data?.[mutationResponseField] ?? null;
}, },

View File

@ -1,5 +1,6 @@
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { apiConfigState } from '@/client-config/states/apiConfigState'; import { apiConfigState } from '@/client-config/states/apiConfigState';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
@ -65,38 +66,54 @@ export const useDestroyManyRecords = ({
(batchIndex + 1) * mutationPageSize, (batchIndex + 1) * mutationPageSize,
); );
const destroyedRecordsResponse = await apolloClient.mutate({ const originalRecords = idsToDestroy
mutation: destroyManyRecordsMutation, .map((recordId) => getRecordFromCache(recordId, apolloClient.cache))
variables: { .filter(isDefined);
filter: { id: { in: batchIds } },
},
optimisticResponse: options?.skipOptimisticEffect
? undefined
: {
[mutationResponseField]: batchIds.map((idToDestroy) => ({
__typename: capitalize(objectNameSingular),
id: idToDestroy,
})),
},
update: options?.skipOptimisticEffect
? undefined
: (cache, { data }) => {
const records = data?.[mutationResponseField];
if (!records?.length) return; const destroyedRecordsResponse = await apolloClient
.mutate({
mutation: destroyManyRecordsMutation,
variables: {
filter: { id: { in: batchIds } },
},
optimisticResponse: options?.skipOptimisticEffect
? undefined
: {
[mutationResponseField]: batchIds.map((idToDestroy) => ({
__typename: capitalize(objectNameSingular),
id: idToDestroy,
})),
},
update: options?.skipOptimisticEffect
? undefined
: (cache, { data }) => {
const records = data?.[mutationResponseField];
const cachedRecords = records if (!records?.length) return;
.map((record) => getRecordFromCache(record.id, cache))
.filter(isDefined);
triggerDeleteRecordsOptimisticEffect({ const cachedRecords = records
cache, .map((record) => getRecordFromCache(record.id, cache))
objectMetadataItem, .filter(isDefined);
recordsToDelete: cachedRecords,
objectMetadataItems, triggerDeleteRecordsOptimisticEffect({
}); cache,
}, objectMetadataItem,
}); recordsToDelete: cachedRecords,
objectMetadataItems,
});
},
})
.catch((error: Error) => {
if (originalRecords.length > 0) {
triggerCreateRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
recordsToCreate: originalRecords,
objectMetadataItems,
});
}
throw error;
});
const destroyedRecordsForThisBatch = const destroyedRecordsForThisBatch =
destroyedRecordsResponse.data?.[mutationResponseField] ?? []; destroyedRecordsResponse.data?.[mutationResponseField] ?? [];

View File

@ -1,12 +1,15 @@
import { useApolloClient } from '@apollo/client'; import { useApolloClient } from '@apollo/client';
import { useCallback } from 'react'; import { useCallback } from 'react';
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField'; import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
import { capitalize } from '~/utils/string/capitalize'; import { capitalize } from '~/utils/string/capitalize';
type useDestroyOneRecordProps = { type useDestroyOneRecordProps = {
@ -38,32 +41,49 @@ export const useDestroyOneRecord = ({
const destroyOneRecord = useCallback( const destroyOneRecord = useCallback(
async (idToDestroy: string) => { async (idToDestroy: string) => {
const deletedRecord = await apolloClient.mutate({ const originalRecord: ObjectRecord | null = getRecordFromCache(
mutation: destroyOneRecordMutation, idToDestroy,
variables: { idToDestroy }, apolloClient.cache,
optimisticResponse: { );
[mutationResponseField]: {
__typename: capitalize(objectNameSingular), const deletedRecord = await apolloClient
id: idToDestroy, .mutate({
mutation: destroyOneRecordMutation,
variables: { idToDestroy },
optimisticResponse: {
[mutationResponseField]: {
__typename: capitalize(objectNameSingular),
id: idToDestroy,
},
}, },
}, update: (cache, { data }) => {
update: (cache, { data }) => { const record = data?.[mutationResponseField];
const record = data?.[mutationResponseField];
if (!record) return; if (!record) return;
const cachedRecord = getRecordFromCache(record.id, cache); const cachedRecord = getRecordFromCache(record.id, cache);
if (!cachedRecord) return; if (!cachedRecord) return;
triggerDeleteRecordsOptimisticEffect({ triggerDeleteRecordsOptimisticEffect({
cache, cache,
objectMetadataItem, objectMetadataItem,
recordsToDelete: [cachedRecord], recordsToDelete: [cachedRecord],
objectMetadataItems, objectMetadataItems,
}); });
}, },
}); })
.catch((error: Error) => {
if (!isUndefinedOrNull(originalRecord)) {
triggerCreateRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
recordsToCreate: [originalRecord],
objectMetadataItems,
});
}
throw error;
});
return deletedRecord.data?.[mutationResponseField] ?? null; return deletedRecord.data?.[mutationResponseField] ?? null;
}, },

View File

@ -62,22 +62,27 @@ export const useRestoreManyRecords = ({
objectMetadataItem.namePlural, objectMetadataItem.namePlural,
)}`; )}`;
const restoredRecordsResponse = await apolloClient.mutate({ const restoredRecordsResponse = await apolloClient
mutation: restoreManyRecordsMutation, .mutate({
refetchQueries: [findOneQueryName, findManyQueryName], mutation: restoreManyRecordsMutation,
variables: { refetchQueries: [findOneQueryName, findManyQueryName],
filter: { id: { in: batchIds } }, variables: {
}, filter: { id: { in: batchIds } },
optimisticResponse: options?.skipOptimisticEffect },
? undefined optimisticResponse: options?.skipOptimisticEffect
: { ? undefined
[mutationResponseField]: batchIds.map((idToRestore) => ({ : {
__typename: capitalize(objectNameSingular), [mutationResponseField]: batchIds.map((idToRestore) => ({
id: idToRestore, __typename: capitalize(objectNameSingular),
deletedAt: null, id: idToRestore,
})), deletedAt: null,
}, })),
}); },
})
.catch((error: Error) => {
// TODO: revert optimistic effect (once optimistic effect is fixed)
throw error;
});
const restoredRecordsForThisBatch = const restoredRecordsForThisBatch =
restoredRecordsResponse.data?.[mutationResponseField] ?? []; restoredRecordsResponse.data?.[mutationResponseField] ?? [];

View File

@ -108,26 +108,48 @@ export const useUpdateOneRecord = <
const mutationResponseField = const mutationResponseField =
getUpdateOneRecordMutationResponseField(objectNameSingular); getUpdateOneRecordMutationResponseField(objectNameSingular);
const updatedRecord = await apolloClient.mutate({ const updatedRecord = await apolloClient
mutation: updateOneRecordMutation, .mutate({
variables: { mutation: updateOneRecordMutation,
idToUpdate, variables: {
input: sanitizedInput, idToUpdate,
}, input: sanitizedInput,
update: (cache, { data }) => { },
const record = data?.[mutationResponseField]; update: (cache, { data }) => {
const record = data?.[mutationResponseField];
if (!record || !cachedRecord) return; if (!record || !cachedRecord) return;
triggerUpdateRecordOptimisticEffect({
cache,
objectMetadataItem,
currentRecord: cachedRecord,
updatedRecord: record,
objectMetadataItems,
});
},
})
.catch((error: Error) => {
if (!cachedRecord) {
throw error;
}
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
record: cachedRecord,
});
triggerUpdateRecordOptimisticEffect({ triggerUpdateRecordOptimisticEffect({
cache, cache: apolloClient.cache,
objectMetadataItem, objectMetadataItem,
currentRecord: cachedRecord, currentRecord: optimisticRecordWithConnection,
updatedRecord: record, updatedRecord: cachedRecordWithConnection,
objectMetadataItems, objectMetadataItems,
}); });
},
}); throw error;
});
return updatedRecord?.data?.[mutationResponseField] ?? null; return updatedRecord?.data?.[mutationResponseField] ?? null;
}; };