refactor: apply relation optimistic effects on record update (#3556)

* refactor: apply relation optimistic effects on record update

Related to #3509

* refactor: remove need to pass relation id field to create and update mutations

* fix: fix tests

* fix: fix SingleEntitySelect glitch

* fix: fix usePersistField tests

* fix: fix wrong import after rebase

* fix: fix several tests

* fix: fix test types
This commit is contained in:
Thaïs
2024-01-29 08:00:00 -03:00
committed by GitHub
parent d66d8c9907
commit a58b4cf437
43 changed files with 970 additions and 1109 deletions

View File

@ -0,0 +1,76 @@
import { useRecoilCallback } from 'recoil';
import { TriggerUpdateRelationFieldOptimisticEffectParams } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationFieldOptimisticEffect';
import { objectMetadataItemFamilySelector } from '@/object-metadata/states/objectMetadataItemFamilySelector';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export const useGetRelationFieldsToOptimisticallyUpdate = () =>
useRecoilCallback(
({ snapshot }) =>
<UpdatedObjectRecord extends ObjectRecord = ObjectRecord>({
cachedRecord,
objectMetadataItem,
updateRecordInput,
}: {
cachedRecord: UpdatedObjectRecord & { __typename: string };
objectMetadataItem: ObjectMetadataItem;
updateRecordInput: Partial<Omit<UpdatedObjectRecord, 'id'>>;
}) =>
Object.entries(updateRecordInput).reduce<
Pick<
TriggerUpdateRelationFieldOptimisticEffectParams,
| 'relationObjectMetadataNameSingular'
| 'relationFieldName'
| 'previousRelationRecord'
| 'nextRelationRecord'
>[]
>((result, [fieldName, nextRelationRecord]) => {
const fieldDefinition = objectMetadataItem.fields.find(
(fieldMetadataItem) => fieldMetadataItem.name === fieldName,
);
if (fieldDefinition?.type !== FieldMetadataType.Relation)
return result;
const relationObjectMetadataNameSingular = (
fieldDefinition.toRelationMetadata?.fromObjectMetadata ||
fieldDefinition.fromRelationMetadata?.toObjectMetadata
)?.nameSingular;
const relationFieldMetadataId =
fieldDefinition.toRelationMetadata?.fromFieldMetadataId ||
fieldDefinition.fromRelationMetadata?.toFieldMetadataId;
if (!relationObjectMetadataNameSingular || !relationFieldMetadataId)
return result;
const relationObjectMetadataItem = snapshot
.getLoadable(
objectMetadataItemFamilySelector({
objectName: relationObjectMetadataNameSingular,
objectNameType: 'singular',
}),
)
.valueOrThrow();
if (!relationObjectMetadataItem) return result;
const relationFieldName = relationObjectMetadataItem.fields.find(
(fieldMetadataItem) =>
fieldMetadataItem.id === relationFieldMetadataId,
)?.name;
if (!relationFieldName) return result;
return [
...result,
{
relationObjectMetadataNameSingular,
relationFieldName,
previousRelationRecord: cachedRecord[fieldName],
nextRelationRecord,
},
];
}, []),
);

View File

@ -0,0 +1,86 @@
import { ApolloCache, StoreObject } from '@apollo/client';
import { isCachedObjectConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectConnection';
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { capitalize } from '~/utils/string/capitalize';
export type TriggerUpdateRelationFieldOptimisticEffectParams = {
cache: ApolloCache<unknown>;
objectNameSingular: string;
record: ObjectRecord;
relationObjectMetadataNameSingular: string;
relationFieldName: string;
previousRelationRecord: ObjectRecord | null;
nextRelationRecord: ObjectRecord | null;
};
export const triggerUpdateRelationFieldOptimisticEffect = ({
cache,
objectNameSingular,
record,
relationObjectMetadataNameSingular,
relationFieldName,
previousRelationRecord,
nextRelationRecord,
}: TriggerUpdateRelationFieldOptimisticEffectParams) => {
const recordTypeName = capitalize(objectNameSingular);
const relationRecordTypeName = capitalize(relationObjectMetadataNameSingular);
if (previousRelationRecord) {
cache.modify<StoreObject>({
id: cache.identify({
...previousRelationRecord,
__typename: relationRecordTypeName,
}),
fields: {
[relationFieldName]: (cachedFieldValue, { isReference, readField }) => {
// To many objects => remove record from previous relation field list
if (isCachedObjectConnection(objectNameSingular, cachedFieldValue)) {
const nextEdges = cachedFieldValue.edges.filter(
({ node }) => readField('id', node) !== record.id,
);
return { ...cachedFieldValue, edges: nextEdges };
}
// To one object => detach previous relation record
if (isReference(cachedFieldValue)) {
return null;
}
},
},
});
}
if (nextRelationRecord) {
cache.modify<StoreObject>({
id: cache.identify({
...nextRelationRecord,
__typename: relationRecordTypeName,
}),
fields: {
[relationFieldName]: (cachedFieldValue, { toReference }) => {
const nodeReference = toReference(record);
if (!nodeReference) return cachedFieldValue;
if (isCachedObjectConnection(objectNameSingular, cachedFieldValue)) {
// To many objects => add record to next relation field list
const nextEdges: CachedObjectRecordEdge[] = [
...cachedFieldValue.edges,
{
__typename: `${recordTypeName}Edge`,
node: nodeReference,
cursor: '',
},
];
return { ...cachedFieldValue, edges: nextEdges };
}
// To one object => attach next relation record
return nodeReference;
},
},
});
}
};