Typed updateRecord hook in generic field logic (#3102)
* Typed updateRecord hook in generic field logic * Use sanitize instead of additional optimisticInput
This commit is contained in:
@ -6,7 +6,11 @@ import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { parseFieldType } from '@/object-metadata/utils/parseFieldType';
|
||||
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
|
||||
import {
|
||||
FieldContext,
|
||||
RecordUpdateHook,
|
||||
RecordUpdateHookParams,
|
||||
} from '@/object-record/field/contexts/FieldContext';
|
||||
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
@ -82,23 +86,11 @@ export const RecordShowPage = () => {
|
||||
? 'Person'
|
||||
: 'Custom';
|
||||
|
||||
const useUpdateOneObjectRecordMutation: () => [
|
||||
(params: any) => any,
|
||||
any,
|
||||
] = () => {
|
||||
const updateEntity = ({
|
||||
variables,
|
||||
}: {
|
||||
variables: {
|
||||
where: { id: string };
|
||||
data: {
|
||||
[fieldName: string]: any;
|
||||
};
|
||||
};
|
||||
}) => {
|
||||
const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id,
|
||||
input: variables.data,
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
@ -172,7 +164,7 @@ export const RecordShowPage = () => {
|
||||
|
||||
await updateOneRecord({
|
||||
idToUpdate: record.id,
|
||||
input: {
|
||||
updateOneRecordInput: {
|
||||
avatarUrl,
|
||||
},
|
||||
});
|
||||
@ -249,8 +241,7 @@ export const RecordShowPage = () => {
|
||||
labelIdentifierFieldMetadata?.name || '',
|
||||
},
|
||||
},
|
||||
useUpdateEntityMutation:
|
||||
useUpdateOneObjectRecordMutation,
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
@ -279,8 +270,7 @@ export const RecordShowPage = () => {
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
}),
|
||||
useUpdateEntityMutation:
|
||||
useUpdateOneObjectRecordMutation,
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -4,6 +4,7 @@ import { useSpreadsheetCompanyImport } from '@/companies/hooks/useSpreadsheetCom
|
||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { RecordUpdateHookParams } from '@/object-record/field/contexts/FieldContext';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { RecordTable } from '@/object-record/record-table/components/RecordTable';
|
||||
import { TableOptionsDropdownId } from '@/object-record/record-table/constants/TableOptionsDropdownId';
|
||||
@ -57,19 +58,10 @@ export const RecordTableContainer = ({
|
||||
recordTableScopeId: recordTableId,
|
||||
});
|
||||
|
||||
const updateEntity = ({
|
||||
variables,
|
||||
}: {
|
||||
variables: {
|
||||
where: { id: string };
|
||||
data: {
|
||||
[fieldName: string]: any;
|
||||
};
|
||||
};
|
||||
}) => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id,
|
||||
input: variables.data,
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
@ -111,14 +103,12 @@ export const RecordTableContainer = ({
|
||||
/>
|
||||
</SpreadsheetImportProvider>
|
||||
<RecordTableEffect recordTableId={recordTableId} viewBarId={viewBarId} />
|
||||
{
|
||||
<RecordTable
|
||||
recordTableId={recordTableId}
|
||||
viewBarId={viewBarId}
|
||||
updateRecordMutation={updateEntity}
|
||||
createRecord={createRecord}
|
||||
/>
|
||||
}
|
||||
<RecordTable
|
||||
recordTableId={recordTableId}
|
||||
viewBarId={viewBarId}
|
||||
updateRecordMutation={updateEntity}
|
||||
createRecord={createRecord}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -3,10 +3,25 @@ import { createContext } from 'react';
|
||||
import { FieldDefinition } from '../types/FieldDefinition';
|
||||
import { FieldMetadata } from '../types/FieldMetadata';
|
||||
|
||||
export type RecordUpdateHookParams = {
|
||||
variables: {
|
||||
where: Record<string, unknown>;
|
||||
updateOneRecordInput: Record<string, unknown>;
|
||||
};
|
||||
};
|
||||
|
||||
export type RecordUpdateHookReturn = {
|
||||
loading?: boolean;
|
||||
};
|
||||
|
||||
export type RecordUpdateHook = () => [
|
||||
(params: RecordUpdateHookParams) => void,
|
||||
RecordUpdateHookReturn,
|
||||
];
|
||||
|
||||
export type GenericFieldContextType = {
|
||||
fieldDefinition: FieldDefinition<FieldMetadata>;
|
||||
// TODO: add better typing for mutation web-hook
|
||||
useUpdateEntityMutation?: () => [(params: any) => void, any];
|
||||
useUpdateRecord?: RecordUpdateHook;
|
||||
entityId: string;
|
||||
recoilScopeId?: string;
|
||||
hotkeyScope: string;
|
||||
|
||||
@ -31,10 +31,10 @@ export const usePersistField = () => {
|
||||
const {
|
||||
entityId,
|
||||
fieldDefinition,
|
||||
useUpdateEntityMutation = () => [],
|
||||
useUpdateRecord = () => [],
|
||||
} = useContext(FieldContext);
|
||||
|
||||
const [updateEntity] = useUpdateEntityMutation();
|
||||
const [updateRecord] = useUpdateRecord();
|
||||
|
||||
const persistField = useRecoilCallback(
|
||||
({ set }) =>
|
||||
@ -85,13 +85,12 @@ export const usePersistField = () => {
|
||||
valueToPersist,
|
||||
);
|
||||
|
||||
updateEntity?.({
|
||||
updateRecord?.({
|
||||
variables: {
|
||||
where: { id: entityId },
|
||||
data: {
|
||||
// TODO: find a more elegant way to do this ?
|
||||
// Maybe have a link between the RELATION field and the UUID field ?
|
||||
updateOneRecordInput: {
|
||||
[`${fieldName}Id`]: valueToPersist?.id ?? null,
|
||||
[`${fieldName}`]: valueToPersist ?? null,
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -113,10 +112,10 @@ export const usePersistField = () => {
|
||||
valueToPersist,
|
||||
);
|
||||
|
||||
updateEntity?.({
|
||||
updateRecord?.({
|
||||
variables: {
|
||||
where: { id: entityId },
|
||||
data: {
|
||||
updateOneRecordInput: {
|
||||
[fieldName]: valueToPersist,
|
||||
},
|
||||
},
|
||||
@ -131,7 +130,7 @@ export const usePersistField = () => {
|
||||
);
|
||||
}
|
||||
},
|
||||
[entityId, fieldDefinition, updateEntity],
|
||||
[entityId, fieldDefinition, updateRecord],
|
||||
);
|
||||
|
||||
return persistField;
|
||||
|
||||
@ -9,10 +9,10 @@ export const useToggleEditOnlyInput = () => {
|
||||
const {
|
||||
entityId,
|
||||
fieldDefinition,
|
||||
useUpdateEntityMutation = () => [],
|
||||
useUpdateRecord = () => [],
|
||||
} = useContext(FieldContext);
|
||||
|
||||
const [updateEntity] = useUpdateEntityMutation();
|
||||
const [updateRecord] = useUpdateRecord();
|
||||
|
||||
const toggleField = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
@ -30,10 +30,10 @@ export const useToggleEditOnlyInput = () => {
|
||||
valueToPersist,
|
||||
);
|
||||
|
||||
updateEntity?.({
|
||||
updateRecord?.({
|
||||
variables: {
|
||||
where: { id: entityId },
|
||||
data: {
|
||||
updateOneRecordInput: {
|
||||
[fieldName]: valueToPersist,
|
||||
},
|
||||
},
|
||||
@ -44,7 +44,7 @@ export const useToggleEditOnlyInput = () => {
|
||||
);
|
||||
}
|
||||
},
|
||||
[entityId, fieldDefinition, updateEntity],
|
||||
[entityId, fieldDefinition, updateRecord],
|
||||
);
|
||||
|
||||
return toggleField;
|
||||
|
||||
@ -22,7 +22,7 @@ export const FieldContextProvider = ({
|
||||
recoilScopeId: '1',
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
fieldDefinition,
|
||||
useUpdateEntityMutation: () => [() => undefined, {}],
|
||||
useUpdateRecord: () => [() => undefined, {}],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -37,7 +37,7 @@ const meta: Meta = {
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
useUpdateEntityMutation: () => [() => undefined, undefined],
|
||||
useUpdateRecord: () => [() => undefined, {}],
|
||||
}}
|
||||
>
|
||||
<NumberFieldValueSetterEffect value={args.value} />
|
||||
|
||||
@ -38,7 +38,7 @@ const meta: Meta = {
|
||||
},
|
||||
},
|
||||
hotkeyScope: 'hotkey-scope',
|
||||
useUpdateEntityMutation: () => [() => undefined, undefined],
|
||||
useUpdateRecord: () => [() => undefined, {}],
|
||||
}}
|
||||
>
|
||||
<MemoryRouter>
|
||||
|
||||
@ -44,22 +44,6 @@ export const RelationFieldInput = ({
|
||||
onCancel={onCancel}
|
||||
initialSearchFilter={initialSearchValue}
|
||||
/>
|
||||
{/* {fieldDefinition.metadata.fieldName === 'person' ? (
|
||||
<PeoplePicker
|
||||
personId={initialValue?.id ?? ''}
|
||||
companyId={initialValue?.companyId ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={onCancel}
|
||||
initialSearchFilter={initialSearchValue}
|
||||
/>
|
||||
) : fieldDefinition.metadata.fieldName === 'company' ? (
|
||||
<CompanyPicker
|
||||
companyId={initialValue?.id ?? ''}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={onCancel}
|
||||
initialSearchFilter={initialSearchValue}
|
||||
/>
|
||||
) : null} */}
|
||||
</StyledRelationPickerContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,7 +2,11 @@ import { ReactNode } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition';
|
||||
import { FieldContext } from '@/object-record/field/contexts/FieldContext';
|
||||
import {
|
||||
FieldContext,
|
||||
RecordUpdateHook,
|
||||
RecordUpdateHookParams,
|
||||
} from '@/object-record/field/contexts/FieldContext';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
|
||||
@ -11,13 +15,11 @@ export const useFieldContext = ({
|
||||
fieldMetadataName,
|
||||
objectRecordId,
|
||||
fieldPosition,
|
||||
forceRefetch,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
objectRecordId: string;
|
||||
fieldMetadataName: string;
|
||||
fieldPosition: number;
|
||||
forceRefetch?: boolean;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
@ -27,25 +29,15 @@ export const useFieldContext = ({
|
||||
(field) => field.name === fieldMetadataName,
|
||||
);
|
||||
|
||||
const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => {
|
||||
const useUpdateOneObjectMutation: RecordUpdateHook = () => {
|
||||
const { updateOneRecord } = useUpdateOneRecord({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const updateEntity = ({
|
||||
variables,
|
||||
}: {
|
||||
variables: {
|
||||
where: { id: string };
|
||||
data: {
|
||||
[fieldName: string]: any;
|
||||
};
|
||||
};
|
||||
}) => {
|
||||
const updateEntity = ({ variables }: RecordUpdateHookParams) => {
|
||||
updateOneRecord?.({
|
||||
idToUpdate: variables.where.id,
|
||||
input: variables.data,
|
||||
forceRefetch,
|
||||
idToUpdate: variables.where.id as string,
|
||||
updateOneRecordInput: variables.updateOneRecordInput,
|
||||
});
|
||||
};
|
||||
|
||||
@ -66,7 +58,7 @@ export const useFieldContext = ({
|
||||
position: fieldPosition,
|
||||
objectMetadataItem,
|
||||
}),
|
||||
useUpdateEntityMutation: useUpdateOneObjectMutation,
|
||||
useUpdateRecord: useUpdateOneObjectMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
|
||||
@ -2,6 +2,7 @@ import { useApolloClient } from '@apollo/client';
|
||||
|
||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
type useUpdateOneRecordProps = {
|
||||
@ -25,37 +26,46 @@ export const useUpdateOneRecord = <T>({
|
||||
|
||||
const updateOneRecord = async ({
|
||||
idToUpdate,
|
||||
input,
|
||||
updateOneRecordInput,
|
||||
}: {
|
||||
idToUpdate: string;
|
||||
input: Record<string, any>;
|
||||
forceRefetch?: boolean;
|
||||
updateOneRecordInput: Record<string, unknown>;
|
||||
}) => {
|
||||
const cachedRecord = getRecordFromCache(idToUpdate);
|
||||
|
||||
const optimisticallyUpdatedRecord: Record<string, any> = {
|
||||
...(cachedRecord ?? {}),
|
||||
...updateOneRecordInput,
|
||||
};
|
||||
|
||||
const sanitizedUpdateOneRecordInput = Object.fromEntries(
|
||||
Object.keys(updateOneRecordInput)
|
||||
.filter((fieldName) => {
|
||||
const fieldDefinition = objectMetadataItem.fields.find(
|
||||
(field) => field.name === fieldName,
|
||||
);
|
||||
|
||||
return fieldDefinition?.type !== FieldMetadataType.Relation;
|
||||
})
|
||||
.map((fieldName) => [fieldName, updateOneRecordInput[fieldName]]),
|
||||
);
|
||||
|
||||
triggerOptimisticEffects({
|
||||
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
||||
updatedRecords: [
|
||||
{
|
||||
...(cachedRecord ?? {}),
|
||||
...input,
|
||||
},
|
||||
],
|
||||
updatedRecords: [optimisticallyUpdatedRecord],
|
||||
});
|
||||
|
||||
const updatedRecord = await apolloClient.mutate({
|
||||
mutation: updateOneRecordMutation,
|
||||
variables: {
|
||||
idToUpdate: idToUpdate,
|
||||
idToUpdate,
|
||||
input: {
|
||||
...input,
|
||||
...sanitizedUpdateOneRecordInput,
|
||||
},
|
||||
},
|
||||
optimisticResponse: {
|
||||
[`update${capitalize(objectMetadataItem.nameSingular)}`]: {
|
||||
...(cachedRecord ?? {}),
|
||||
...input,
|
||||
},
|
||||
[`update${capitalize(objectMetadataItem.nameSingular)}`]:
|
||||
optimisticallyUpdatedRecord,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ export const RecordBoard = ({
|
||||
async (pipelineProgressId: string, pipelineStepId: string) => {
|
||||
await updateOneOpportunity?.({
|
||||
idToUpdate: pipelineProgressId,
|
||||
input: {
|
||||
updateOneRecordInput: {
|
||||
pipelineStepId: pipelineStepId,
|
||||
},
|
||||
});
|
||||
|
||||
@ -26,7 +26,7 @@ export const useBoardColumnsInternal = () => {
|
||||
(stage) =>
|
||||
updateOnePipelineStep?.({
|
||||
idToUpdate: stage.id,
|
||||
input: {
|
||||
updateOneRecordInput: {
|
||||
position: stage.position,
|
||||
},
|
||||
}),
|
||||
|
||||
@ -12,7 +12,7 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useViewFields } from '@/views/hooks/internal/useViewFields';
|
||||
import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField';
|
||||
|
||||
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||
import { useRecordTable } from '../hooks/useRecordTable';
|
||||
import { RecordTableScope } from '../scopes/RecordTableScope';
|
||||
import { numberOfTableRowsState } from '../states/numberOfTableRowsState';
|
||||
@ -159,7 +159,7 @@ export const RecordTable = ({
|
||||
})}
|
||||
>
|
||||
<ScrollWrapper>
|
||||
<EntityUpdateMutationContext.Provider value={updateRecordMutation}>
|
||||
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
||||
<StyledTableWithHeader>
|
||||
<StyledTableContainer>
|
||||
<div ref={tableBodyRef}>
|
||||
@ -193,7 +193,7 @@ export const RecordTable = ({
|
||||
)}
|
||||
</StyledTableContainer>
|
||||
</StyledTableWithHeader>
|
||||
</EntityUpdateMutationContext.Provider>
|
||||
</RecordUpdateContext.Provider>
|
||||
</ScrollWrapper>
|
||||
</RecordTableScope>
|
||||
);
|
||||
|
||||
@ -11,7 +11,7 @@ import { FieldContext } from '../../field/contexts/FieldContext';
|
||||
import { isFieldRelation } from '../../field/types/guards/isFieldRelation';
|
||||
import { ColumnContext } from '../contexts/ColumnContext';
|
||||
import { ColumnIndexContext } from '../contexts/ColumnIndexContext';
|
||||
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||
import { RecordUpdateContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||
import { RowIdContext } from '../contexts/RowIdContext';
|
||||
import { TableCell } from '../record-table-cell/components/RecordTableCell';
|
||||
import { useCurrentRowSelected } from '../record-table-row/hooks/useCurrentRowSelected';
|
||||
@ -39,7 +39,7 @@ export const RecordTableCell = ({ cellIndex }: { cellIndex: number }) => {
|
||||
|
||||
const columnDefinition = useContext(ColumnContext);
|
||||
|
||||
const updateEntityMutation = useContext(EntityUpdateMutationContext);
|
||||
const updateRecord = useContext(RecordUpdateContext);
|
||||
|
||||
if (!columnDefinition || !currentRowId) {
|
||||
return null;
|
||||
@ -58,7 +58,7 @@ export const RecordTableCell = ({ cellIndex }: { cellIndex: number }) => {
|
||||
recoilScopeId: currentRowId + columnDefinition.label,
|
||||
entityId: currentRowId,
|
||||
fieldDefinition: columnDefinition,
|
||||
useUpdateEntityMutation: () => [updateEntityMutation, {}],
|
||||
useUpdateRecord: () => [updateRecord, {}],
|
||||
hotkeyScope: customHotkeyScope,
|
||||
basePathToShowPage: objectMetadataConfig?.basePathToShowPage,
|
||||
isLabelIdentifier:
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const EntityUpdateMutationContext = createContext<(params: any) => void>(
|
||||
{} as any,
|
||||
);
|
||||
import { RecordUpdateHookParams } from '@/object-record/field/contexts/FieldContext';
|
||||
|
||||
export const RecordUpdateContext = createContext<
|
||||
(params: RecordUpdateHookParams) => void
|
||||
>({} as any);
|
||||
|
||||
Reference in New Issue
Block a user