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:
@ -2,9 +2,11 @@ import { useApolloClient } from '@apollo/client';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
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 { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
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) {
|
||||
if (recordToCreate.id === null) {
|
||||
@ -98,7 +100,8 @@ export const useCreateManyRecords = <
|
||||
objectMetadataItem.namePlural,
|
||||
);
|
||||
|
||||
const createdObjects = await apolloClient.mutate({
|
||||
const createdObjects = await apolloClient
|
||||
.mutate({
|
||||
mutation: createManyRecordsMutation,
|
||||
variables: {
|
||||
data: sanitizedCreateManyRecordsInput,
|
||||
@ -117,6 +120,25 @@ export const useCreateManyRecords = <
|
||||
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] ?? [];
|
||||
|
||||
@ -3,9 +3,11 @@ import { useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
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 { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields';
|
||||
import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields';
|
||||
@ -85,7 +87,8 @@ export const useCreateOneRecord = <
|
||||
const mutationResponseField =
|
||||
getCreateOneRecordMutationResponseField(objectNameSingular);
|
||||
|
||||
const createdObject = await apolloClient.mutate({
|
||||
const createdObject = await apolloClient
|
||||
.mutate({
|
||||
mutation: createOneRecordMutation,
|
||||
variables: {
|
||||
input: sanitizedInput,
|
||||
@ -105,6 +108,27 @@ export const useCreateOneRecord = <
|
||||
|
||||
setLoading(false);
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (!recordCreatedInCache) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
deleteRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloClient.cache,
|
||||
recordToDelete: recordCreatedInCache,
|
||||
});
|
||||
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache: apolloClient.cache,
|
||||
objectMetadataItem,
|
||||
recordsToDelete: [recordCreatedInCache],
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
return createdObject.data?.[mutationResponseField] ?? null;
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { apiConfigState } from '@/client-config/states/apiConfigState';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
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 { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation';
|
||||
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField';
|
||||
@ -65,7 +67,8 @@ export const useDeleteManyRecords = ({
|
||||
(batchIndex + 1) * mutationPageSize,
|
||||
);
|
||||
|
||||
const deletedRecordsResponse = await apolloClient.mutate({
|
||||
const deletedRecordsResponse = await apolloClient
|
||||
.mutate({
|
||||
mutation: deleteManyRecordsMutation,
|
||||
variables: {
|
||||
filter: { id: { in: batchIds } },
|
||||
@ -96,6 +99,41 @@ export const useDeleteManyRecords = ({
|
||||
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,
|
||||
})),
|
||||
});
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
const deletedRecordsForThisBatch =
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||
import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation';
|
||||
import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
@ -39,7 +41,8 @@ export const useDeleteOneRecord = ({
|
||||
async (idToDelete: string) => {
|
||||
const currentTimestamp = new Date().toISOString();
|
||||
|
||||
const deletedRecord = await apolloClient.mutate({
|
||||
const deletedRecord = await apolloClient
|
||||
.mutate({
|
||||
mutation: deleteOneRecordMutation,
|
||||
variables: {
|
||||
idToDelete: idToDelete,
|
||||
@ -67,6 +70,40 @@ export const useDeleteOneRecord = ({
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
const cachedRecord = getRecordFromCache(
|
||||
idToDelete,
|
||||
apolloClient.cache,
|
||||
);
|
||||
|
||||
if (!cachedRecord) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
updateRecordFromCache({
|
||||
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;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { apiConfigState } from '@/client-config/states/apiConfigState';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
@ -65,7 +66,12 @@ export const useDestroyManyRecords = ({
|
||||
(batchIndex + 1) * mutationPageSize,
|
||||
);
|
||||
|
||||
const destroyedRecordsResponse = await apolloClient.mutate({
|
||||
const originalRecords = idsToDestroy
|
||||
.map((recordId) => getRecordFromCache(recordId, apolloClient.cache))
|
||||
.filter(isDefined);
|
||||
|
||||
const destroyedRecordsResponse = await apolloClient
|
||||
.mutate({
|
||||
mutation: destroyManyRecordsMutation,
|
||||
variables: {
|
||||
filter: { id: { in: batchIds } },
|
||||
@ -96,6 +102,17 @@ export const useDestroyManyRecords = ({
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (originalRecords.length > 0) {
|
||||
triggerCreateRecordsOptimisticEffect({
|
||||
cache: apolloClient.cache,
|
||||
objectMetadataItem,
|
||||
recordsToCreate: originalRecords,
|
||||
objectMetadataItems,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
const destroyedRecordsForThisBatch =
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
type useDestroyOneRecordProps = {
|
||||
@ -38,7 +41,13 @@ export const useDestroyOneRecord = ({
|
||||
|
||||
const destroyOneRecord = useCallback(
|
||||
async (idToDestroy: string) => {
|
||||
const deletedRecord = await apolloClient.mutate({
|
||||
const originalRecord: ObjectRecord | null = getRecordFromCache(
|
||||
idToDestroy,
|
||||
apolloClient.cache,
|
||||
);
|
||||
|
||||
const deletedRecord = await apolloClient
|
||||
.mutate({
|
||||
mutation: destroyOneRecordMutation,
|
||||
variables: { idToDestroy },
|
||||
optimisticResponse: {
|
||||
@ -63,6 +72,17 @@ export const useDestroyOneRecord = ({
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (!isUndefinedOrNull(originalRecord)) {
|
||||
triggerCreateRecordsOptimisticEffect({
|
||||
cache: apolloClient.cache,
|
||||
objectMetadataItem,
|
||||
recordsToCreate: [originalRecord],
|
||||
objectMetadataItems,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
return deletedRecord.data?.[mutationResponseField] ?? null;
|
||||
|
||||
@ -62,7 +62,8 @@ export const useRestoreManyRecords = ({
|
||||
objectMetadataItem.namePlural,
|
||||
)}`;
|
||||
|
||||
const restoredRecordsResponse = await apolloClient.mutate({
|
||||
const restoredRecordsResponse = await apolloClient
|
||||
.mutate({
|
||||
mutation: restoreManyRecordsMutation,
|
||||
refetchQueries: [findOneQueryName, findManyQueryName],
|
||||
variables: {
|
||||
@ -77,6 +78,10 @@ export const useRestoreManyRecords = ({
|
||||
deletedAt: null,
|
||||
})),
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
// TODO: revert optimistic effect (once optimistic effect is fixed)
|
||||
throw error;
|
||||
});
|
||||
|
||||
const restoredRecordsForThisBatch =
|
||||
|
||||
@ -108,7 +108,8 @@ export const useUpdateOneRecord = <
|
||||
const mutationResponseField =
|
||||
getUpdateOneRecordMutationResponseField(objectNameSingular);
|
||||
|
||||
const updatedRecord = await apolloClient.mutate({
|
||||
const updatedRecord = await apolloClient
|
||||
.mutate({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate,
|
||||
@ -127,6 +128,27 @@ export const useUpdateOneRecord = <
|
||||
objectMetadataItems,
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (!cachedRecord) {
|
||||
throw error;
|
||||
}
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloClient.cache,
|
||||
record: cachedRecord,
|
||||
});
|
||||
|
||||
triggerUpdateRecordOptimisticEffect({
|
||||
cache: apolloClient.cache,
|
||||
objectMetadataItem,
|
||||
currentRecord: optimisticRecordWithConnection,
|
||||
updatedRecord: cachedRecordWithConnection,
|
||||
objectMetadataItems,
|
||||
});
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
return updatedRecord?.data?.[mutationResponseField] ?? null;
|
||||
|
||||
Reference in New Issue
Block a user