Skip to content

Commit

Permalink
Revert optimistic rendering on negative response (#7541)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
FelixMalfait authored Oct 9, 2024
1 parent f901512 commit be49d4f
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 168 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -67,7 +69,7 @@ export const useCreateManyRecords = <
},
);

const recordsCreatedInCache = [];
const recordsCreatedInCache: ObjectRecord[] = [];

for (const recordToCreate of sanitizedCreateManyRecordsInput) {
if (recordToCreate.id === null) {
Expand Down Expand Up @@ -98,26 +100,46 @@ export const useCreateManyRecords = <
objectMetadataItem.namePlural,
);

const createdObjects = await apolloClient.mutate({
mutation: createManyRecordsMutation,
variables: {
data: sanitizedCreateManyRecordsInput,
upsert: upsert,
},
update: (cache, { data }) => {
const records = data?.[mutationResponseField];
const createdObjects = await apolloClient
.mutate({
mutation: createManyRecordsMutation,
variables: {
data: sanitizedCreateManyRecordsInput,
upsert: upsert,
},
update: (cache, { data }) => {
const records = data?.[mutationResponseField];

if (!records?.length || skipPostOptmisticEffect) return;

if (!records?.length || skipPostOptmisticEffect) return;
triggerCreateRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToCreate: records,
objectMetadataItems,
shouldMatchRootQueryFilter,
});
},
})
.catch((error: Error) => {
recordsCreatedInCache.forEach((recordToDelete) => {
deleteRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
recordToDelete,
});
});

triggerCreateRecordsOptimisticEffect({
cache,
triggerDeleteRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
recordsToCreate: records,
recordsToDelete: recordsCreatedInCache,
objectMetadataItems,
shouldMatchRootQueryFilter,
});
},
});

throw error;
});

return createdObjects.data?.[mutationResponseField] ?? [];
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -85,27 +87,49 @@ export const useCreateOneRecord = <
const mutationResponseField =
getCreateOneRecordMutationResponseField(objectNameSingular);

const createdObject = await apolloClient.mutate({
mutation: createOneRecordMutation,
variables: {
input: sanitizedInput,
},
update: (cache, { data }) => {
const record = data?.[mutationResponseField];

if (!record || skipPostOptmisticEffect) return;
const createdObject = await apolloClient
.mutate({
mutation: createOneRecordMutation,
variables: {
input: sanitizedInput,
},
update: (cache, { data }) => {
const record = data?.[mutationResponseField];

if (!record || skipPostOptmisticEffect) return;

triggerCreateRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToCreate: [record],
objectMetadataItems,
shouldMatchRootQueryFilter,
});

setLoading(false);
},
})
.catch((error: Error) => {
if (!recordCreatedInCache) {
throw error;
}

deleteRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
recordToDelete: recordCreatedInCache,
});

triggerCreateRecordsOptimisticEffect({
cache,
triggerDeleteRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
recordsToCreate: [record],
recordsToDelete: [recordCreatedInCache],
objectMetadataItems,
shouldMatchRootQueryFilter,
});

setLoading(false);
},
});
throw error;
});

return createdObject.data?.[mutationResponseField] ?? null;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -65,38 +67,74 @@ export const useDeleteManyRecords = ({
(batchIndex + 1) * mutationPageSize,
);

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,
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];

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;

const cachedRecords = records
.map((record) => getRecordFromCache(record.id, cache))
.filter(isDefined);

triggerDeleteRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToDelete: cachedRecords,
objectMetadataItems,
});
},
});
});

throw error;
});

const deletedRecordsForThisBatch =
deletedRecordsResponse.data?.[mutationResponseField] ?? [];
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -39,35 +41,70 @@ export const useDeleteOneRecord = ({
async (idToDelete: string) => {
const currentTimestamp = new Date().toISOString();

const deletedRecord = await apolloClient.mutate({
mutation: deleteOneRecordMutation,
variables: {
idToDelete: idToDelete,
},
optimisticResponse: {
[mutationResponseField]: {
__typename: capitalize(objectNameSingular),
id: idToDelete,
deletedAt: currentTimestamp,
const deletedRecord = await apolloClient
.mutate({
mutation: deleteOneRecordMutation,
variables: {
idToDelete: idToDelete,
},
},
update: (cache, { data }) => {
const record = data?.[mutationResponseField];
optimisticResponse: {
[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({
cache,
triggerDeleteRecordsOptimisticEffect({
cache,
objectMetadataItem,
recordsToDelete: [cachedRecord],
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,
recordsToDelete: [cachedRecord],
objectMetadataItems,
recordsToCreate: [
{
...cachedRecord,
deletedAt: null,
},
],
});
},
});

throw error;
});

return deletedRecord.data?.[mutationResponseField] ?? null;
},
Expand Down
Loading

0 comments on commit be49d4f

Please sign in to comment.