Skip to content

Commit 0f8d9c2

Browse files
committed
Abstract useGridMutation
This does a couple of things: - It DRYs the reasoning why don't await results & use optimistic responses. - It drastically pares down the isEqual input to what could be changed.
1 parent 2a669c6 commit 0f8d9c2

File tree

3 files changed

+74
-49
lines changed

3 files changed

+74
-49
lines changed
Lines changed: 17 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,25 @@
1-
import { useMutation } from '@apollo/client';
2-
import { isEqual } from 'lodash';
3-
import { UpdateLanguageEngagement as UpdateLanguageEngagementInput } from '~/api/schema.graphql';
4-
import { EngagementDataGridRowFragment } from './engagementDataGridRow.graphql';
1+
import { useGridMutation } from '../Grid';
2+
import { EngagementDataGridRowFragmentDoc as EngagementGridRow } from './engagementDataGridRow.graphql';
53
import { UpdateLanguageEngagementGridDocument as UpdateLanguageEngagement } from './UpdateLanguageEngagementGrid.graphql';
64

7-
export const useProcessEngagementUpdate = () => {
8-
const [updateLanguageEngagement] = useMutation(UpdateLanguageEngagement);
9-
10-
return (
11-
updated: EngagementDataGridRowFragment,
12-
prev: EngagementDataGridRowFragment
13-
) => {
14-
if (updated.__typename !== 'LanguageEngagement') {
15-
return updated;
16-
}
17-
18-
if (isEqual(updated, prev)) {
19-
return updated;
5+
export const useProcessEngagementUpdate = () =>
6+
useGridMutation(EngagementGridRow, UpdateLanguageEngagement, (row) => {
7+
if (row.__typename !== 'LanguageEngagement') {
8+
return undefined;
209
}
21-
22-
const input: UpdateLanguageEngagementInput = {
23-
id: updated.id,
24-
milestoneReached: updated.milestoneReached.value,
25-
usingAIAssistedTranslation: updated.usingAIAssistedTranslation.value,
26-
};
27-
// Don't wait for the mutation to finish/error, which allows
28-
// the grid to close the editing state immediately.
29-
// There shouldn't be any business errors from these current changes,
30-
// and network errors are handled with snackbars.
31-
// Additionally, MUI doesn't handle thrown errors either; it just gives
32-
// them straight back to us on the `onProcessRowUpdateError` callback.
33-
void updateLanguageEngagement({
34-
variables: { input },
35-
// Inform Apollo of these async/queued updates.
36-
// This is important because users can make multiple changes
37-
// quickly, since we don't `await` above.
38-
// These optimistic updates are layered/stacked.
39-
// So if two value changes are in flight, the value from the first
40-
// API response isn't presented as the latest value,
41-
// since there is still another optimistic update left.
42-
// Said another way: this prevents the UI from presenting the final change,
43-
// then looking like it reverted to the first change,
44-
// and then flipping back to the final change again.
45-
// This also ensures these pending updates are maintained
46-
// even if the grid is unmounted/remounted.
10+
return {
11+
variables: {
12+
input: {
13+
id: row.id,
14+
milestoneReached: row.milestoneReached.value,
15+
usingAIAssistedTranslation: row.usingAIAssistedTranslation.value,
16+
},
17+
},
4718
optimisticResponse: {
4819
updateLanguageEngagement: {
4920
__typename: 'UpdateLanguageEngagementOutput',
50-
engagement: updated,
21+
engagement: row,
5122
},
5223
},
53-
});
54-
55-
return updated;
56-
};
57-
};
24+
};
25+
});

src/components/Grid/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export * from './QuickFilters';
1313
export * from './ColumnTypes/multiEnumColumn';
1414
export * from './ColumnTypes/dateColumn';
1515
export * from './isCellEditable';
16+
export * from './useGridMutation';
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useMutation } from '@apollo/client';
2+
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
3+
import { isEqual } from 'lodash';
4+
5+
export const useGridMutation = <Row, MutationRes, Vars>(
6+
rowShape: DocumentNode<Row>,
7+
mutation: DocumentNode<MutationRes, Vars>,
8+
makeInput: NoInfer<
9+
(object: Row) =>
10+
| {
11+
variables: Vars;
12+
optimisticResponse: MutationRes;
13+
}
14+
| undefined
15+
>
16+
) => {
17+
const [update] = useMutation(mutation);
18+
19+
return (updatedRow: Row, prevRow: Row) => {
20+
const updatedInput = makeInput(updatedRow);
21+
const prevInput = makeInput(prevRow);
22+
23+
if (!updatedInput || !prevInput) {
24+
return updatedRow;
25+
}
26+
27+
if (isEqual(updatedInput.variables, prevInput.variables)) {
28+
return updatedRow;
29+
}
30+
31+
// Don't wait for the mutation to finish/error, which allows
32+
// the grid to close the editing state immediately.
33+
// There shouldn't be any business errors from these current changes,
34+
// and network errors are handled with snackbars.
35+
// Additionally, MUI doesn't handle thrown errors either; it just gives
36+
// them straight back to us on the `onProcessRowUpdateError` callback.
37+
void update({
38+
variables: updatedInput.variables,
39+
// Inform Apollo of these async/queued updates.
40+
// This is important because users can make multiple changes
41+
// quickly, since we don't `await` above.
42+
// These optimistic updates are layered/stacked.
43+
// So if two value changes are in flight, the value from the first
44+
// API response isn't presented as the latest value,
45+
// since there is still another optimistic update left.
46+
// Said another way: this prevents the UI from presenting the final change,
47+
// then looking like it reverted to the first change,
48+
// and then flipping back to the final change again.
49+
// This also ensures these pending updates are maintained
50+
// even if the grid is unmounted/remounted.
51+
optimisticResponse: updatedInput.optimisticResponse,
52+
});
53+
54+
return updatedRow;
55+
};
56+
};

0 commit comments

Comments
 (0)