fix: detach relation records in cache on record deletion (#3707)

* fix: detach relation records in cache on record deletion

* fix: fix useGetRelationMetadata tests
This commit is contained in:
Thaïs
2024-01-31 07:36:26 -03:00
committed by GitHub
parent 9597b1ae41
commit 29339ef99a
19 changed files with 465 additions and 325 deletions

View File

@ -15,6 +15,7 @@ export const useGetRecordFromCache = ({
return <CachedObjectRecord extends ObjectRecord = ObjectRecord>(
recordId: string,
cache = apolloClient.cache,
) => {
if (!objectMetadataItem) {
return null;
@ -31,7 +32,6 @@ export const useGetRecordFromCache = ({
}
`;
const cache = apolloClient.cache;
const cachedRecordId = cache.identify({
__typename: capitalize(objectMetadataItem.nameSingular),
id: recordId,

View File

@ -1,9 +1,14 @@
import { useApolloClient } from '@apollo/client';
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getDeleteManyRecordsMutationResponseField } from '@/object-record/hooks/useGenerateDeleteManyRecordMutation';
import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
import { isDefined } from '~/utils/isDefined';
import { capitalize } from '~/utils/string/capitalize';
type useDeleteOneRecordProps = {
@ -14,9 +19,11 @@ type useDeleteOneRecordProps = {
export const useDeleteManyRecords = ({
objectNameSingular,
}: useDeleteOneRecordProps) => {
const { objectMetadataItem, deleteManyRecordsMutation } =
const { objectMetadataItem, deleteManyRecordsMutation, getRecordFromCache } =
useObjectMetadataItem({ objectNameSingular });
const getRelationMetadata = useGetRelationMetadata();
const apolloClient = useApolloClient();
const mutationResponseField = getDeleteManyRecordsMutationResponseField(
@ -24,16 +31,10 @@ export const useDeleteManyRecords = ({
);
const deleteManyRecords = async (idsToDelete: string[]) => {
const deleteRecordFilter: ObjectRecordQueryFilter = {
id: {
in: idsToDelete,
},
};
const deletedRecords = await apolloClient.mutate({
mutation: deleteManyRecordsMutation,
variables: {
filter: deleteRecordFilter,
// atMost: idsToDelete.length,
filter: { id: { in: idsToDelete } },
},
optimisticResponse: {
[mutationResponseField]: idsToDelete.map((idToDelete) => ({
@ -46,10 +47,49 @@ export const useDeleteManyRecords = ({
if (!records?.length) return;
objectMetadataItem.fields.forEach((fieldMetadataItem) => {
const relationMetadata = getRelationMetadata({ fieldMetadataItem });
if (!relationMetadata) return;
const { relationObjectMetadataItem, relationFieldMetadataItem } =
relationMetadata;
records.forEach((record) => {
const cachedRecord = getRecordFromCache(record.id, cache);
if (!cachedRecord) return;
const previousFieldValue:
| ObjectRecordConnection
| ObjectRecord
| null = cachedRecord[fieldMetadataItem.name];
const relationRecordIds = isObjectRecordConnection(
relationObjectMetadataItem.nameSingular,
previousFieldValue,
)
? previousFieldValue.edges.map(({ node }) => node.id)
: [previousFieldValue?.id].filter(isDefined);
relationRecordIds.forEach((relationRecordId) =>
triggerDetachRelationOptimisticEffect({
cache,
objectNameSingular,
recordId: record.id,
relationObjectMetadataNameSingular:
relationObjectMetadataItem.nameSingular,
relationFieldName: relationFieldMetadataItem.name,
relationRecordId,
}),
);
});
});
triggerDeleteRecordsOptimisticEffect({
cache,
objectMetadataItem,
records,
records: records,
});
},
});

View File

@ -1,9 +1,15 @@
import { useCallback } from 'react';
import { useApolloClient } from '@apollo/client';
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/generateDeleteOneRecordMutation';
import { isDefined } from '~/utils/isDefined';
import { capitalize } from '~/utils/string/capitalize';
type useDeleteOneRecordProps = {
@ -14,9 +20,10 @@ type useDeleteOneRecordProps = {
export const useDeleteOneRecord = ({
objectNameSingular,
}: useDeleteOneRecordProps) => {
const { objectMetadataItem, deleteOneRecordMutation } = useObjectMetadataItem(
{ objectNameSingular },
);
const { objectMetadataItem, deleteOneRecordMutation, getRecordFromCache } =
useObjectMetadataItem({ objectNameSingular });
const getRelationMetadata = useGetRelationMetadata();
const apolloClient = useApolloClient();
@ -39,6 +46,43 @@ export const useDeleteOneRecord = ({
if (!record) return;
objectMetadataItem.fields.forEach((fieldMetadataItem) => {
const relationMetadata = getRelationMetadata({ fieldMetadataItem });
if (!relationMetadata) return;
const { relationObjectMetadataItem, relationFieldMetadataItem } =
relationMetadata;
const cachedRecord = getRecordFromCache(record.id, cache);
if (!cachedRecord) return;
const previousFieldValue:
| ObjectRecordConnection
| ObjectRecord
| null = cachedRecord[fieldMetadataItem.name];
const relationRecordIds = isObjectRecordConnection(
relationObjectMetadataItem.nameSingular,
previousFieldValue,
)
? previousFieldValue.edges.map(({ node }) => node.id)
: [previousFieldValue?.id].filter(isDefined);
relationRecordIds.forEach((relationRecordId) =>
triggerDetachRelationOptimisticEffect({
cache,
objectNameSingular,
recordId: record.id,
relationObjectMetadataNameSingular:
relationObjectMetadataItem.nameSingular,
relationFieldName: relationFieldMetadataItem.name,
relationRecordId,
}),
);
});
triggerDeleteRecordsOptimisticEffect({
cache,
objectMetadataItem,
@ -52,6 +96,8 @@ export const useDeleteOneRecord = ({
[
apolloClient,
deleteOneRecordMutation,
getRecordFromCache,
getRelationMetadata,
mutationResponseField,
objectMetadataItem,
objectNameSingular,

View File

@ -1,12 +1,15 @@
import { useApolloClient } from '@apollo/client';
import { useGetRelationFieldsToOptimisticallyUpdate } from '@/apollo/optimistic-effect/hooks/useGetRelationFieldsToOptimisticallyUpdate';
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { triggerUpdateRelationFieldOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationFieldOptimisticEffect';
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { getUpdateOneRecordMutationResponseField } from '@/object-record/hooks/useGenerateUpdateOneRecordMutation';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { capitalize } from '~/utils/string/capitalize';
type useUpdateOneRecordProps = {
@ -21,8 +24,7 @@ export const useUpdateOneRecord = <
const { objectMetadataItem, updateOneRecordMutation, getRecordFromCache } =
useObjectMetadataItem({ objectNameSingular });
const getRelationFieldsToOptimisticallyUpdate =
useGetRelationFieldsToOptimisticallyUpdate();
const getRelationMetadata = useGetRelationMetadata();
const apolloClient = useApolloClient();
@ -48,14 +50,6 @@ export const useUpdateOneRecord = <
id: idToUpdate,
};
const updatedRelationFields = cachedRecord
? getRelationFieldsToOptimisticallyUpdate({
cachedRecord,
objectMetadataItem,
updateRecordInput: updateOneRecordInput,
})
: [];
const mutationResponseField =
getUpdateOneRecordMutationResponseField(objectNameSingular);
@ -73,29 +67,59 @@ export const useUpdateOneRecord = <
if (!record) return;
objectMetadataItem.fields.forEach((fieldMetadataItem) => {
const relationMetadata = getRelationMetadata({ fieldMetadataItem });
if (!relationMetadata) return;
const { relationObjectMetadataItem, relationFieldMetadataItem } =
relationMetadata;
const previousFieldValue = cachedRecord?.[fieldMetadataItem.name];
const nextFieldValue =
updateOneRecordInput[fieldMetadataItem.name] ?? null;
if (
!(fieldMetadataItem.name in updateOneRecordInput) ||
isObjectRecordConnection(
relationObjectMetadataItem.nameSingular,
previousFieldValue,
) ||
isDeeplyEqual(previousFieldValue, nextFieldValue)
) {
return;
}
if (previousFieldValue) {
triggerDetachRelationOptimisticEffect({
cache,
objectNameSingular,
recordId: record.id,
relationObjectMetadataNameSingular:
relationObjectMetadataItem.nameSingular,
relationFieldName: relationFieldMetadataItem.name,
relationRecordId: previousFieldValue.id,
});
}
if (nextFieldValue) {
triggerAttachRelationOptimisticEffect({
cache,
objectNameSingular,
recordId: record.id,
relationObjectMetadataNameSingular:
relationObjectMetadataItem.nameSingular,
relationFieldName: relationFieldMetadataItem.name,
relationRecordId: nextFieldValue.id,
});
}
});
triggerUpdateRecordOptimisticEffect({
cache,
objectMetadataItem,
record,
});
updatedRelationFields.forEach(
({
relationObjectMetadataNameSingular,
relationFieldName,
previousRelationRecord,
nextRelationRecord,
}) =>
triggerUpdateRelationFieldOptimisticEffect({
cache,
objectNameSingular,
record,
relationObjectMetadataNameSingular,
relationFieldName,
previousRelationRecord,
nextRelationRecord,
}),
);
},
});