PR Review Feedback

I do not have a good memory and cannot remember all the mistakes I made in the previous PR reviewing. Since The palest ink is better than the best memory, so let me summarize the mistakes I have made here to reminder myself, hope it may help.

React

Necessary of useEffect?

When we use the useEffect, we need to think about if it's necessary, especially for the initial mount effect. Instead of updating some values based on the dependency of useEffect, we should check if it's possible to set the value initially directly.

React-query

Use the query data as source of data, instead of triggering/update from UI

One example would be undoing the editing to a table item with Material UI's DataGrid. The procedure of undo is

  1. Edit the table cell text with Material UI's editable function (Imperative)
  2. Trigger the undo by clicking somewhere undo button/link (Declarative)

Here we have two ways to trigger the undo:

  1. use Material UI's method to recover the data in the table and then the data would be updated sequentially
 1undoCallback: async () => {
 2  // Undo the process should be fast with update only one field value
 3  setUndoStateMap((value) => ({ ...value, [newRow.id]: key }))
 4  // during the undo, use data grid's way to update the cell value manually
 5  await tableApiRef.current.startCellEditMode({ id: newRow.id, field: key })
 6  await tableApiRef.current.setEditCellValue({
 7      id: newRow.id,
 8      field: key,
 9      value: oldValue,
10  })
11  // If the cell is in edit mode, stop it
12-   if (tableApiRef.current.getCellMode(newRow.id, key) === 'edit')
13-     await tableApiRef.current.stopCellEditMode({ id: newRow.id, field: key })
14}
  1. update the table cell data directly and it would be reflected by React automatically
1undoCallback: async () => {
2  updateKnowTheDriverMutation.mutate({
3    vehicleId: newRow.vehicleId,
4    category: key,
5    score: oldValue,
6  })
7},

From the code we can see the second one is better since it's simpler and meets the react's principle declarative way. The imperative way needs to know each step of whole undo processing and it may contribute the mistake.

Think of the rollback situation when using mutation

When we mutate the data, we need to think in this way: if the action fails, do we need to rollback the previous value? Here are the example codes about mutation:

 1onMutate: async (variables) => {
 2  const knowTheDriverQuery = knowTheDriverListQuery()
 3
 4  // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
 5  await queryClient.cancelQueries(knowTheDriverQuery)
 6
 7  const previousQueryData = knowTheDriverQuery.getData(queryClient)
 8
 9  if (previousQueryData) {
10    knowTheDriverQuery.setData(queryClient, {
11      updater: updateFirstWhere(
12        previousQueryData,
13        (c) => c.vehicleId === variables.vehicleId,
14        // Explicit return type so that we don't add properties by mistake
15        (c): (typeof previousQueryData)[number] => ({
16          ...c,
17          [variables.category]: Number(variables.score),
18        }),
19      ),
20    })
21  }
22
23  return { previousQueryData }
24},
25onError: (err, _variables, context) => {
26  if (context?.previousQueryData) {
27    knowTheDriverListQuery().setData(queryClient, {
28      updater: context.previousQueryData,
29    })
30  }
31  makeMutationErrorHandlerWithToast().onError(err)
32}

Date

Send Js native Date to server, it would be serialized automatically and use this as the format in API

When we need to send the date to server, no matter what library we use to handle it, Luxon, Moment, etc, we should save the native Date to sever since it's native supported and could be used in all the browsers without supporting from third-part libraries.

Typescript

Parse data from external sources

When we get some data which may be arbitrary from external source, we can use zod to parse the value, because Typescript cannot check the values in the runtime.

For example, we have a field about filters' initial values in the response of api, and it should have static options:

  • vehicle_group_init,
  • department_init,
  • sub_department_init,
  • drivers_init,
  • event_type_init,
1export const apiCoachingDashboardFilterType = z.enum([
2  'vehicle_group_init',
3  'department_init',
4  'sub_department_init',
5  'drivers_init',
6  'event_type_init',
7])

In frontend, we cannot confirm the values sent from backend are correct. So before converting the values, we need to check if they meet our requirements

 1export declare namespace CoachingDashboardFilterType {
 2  type ApiInput = {
 3    id: z.infer<typeof apiCoachingDashboardFilterType>
 4    default: string
 5  }
 6  type ParsedEvent = {
 7    id: 'vehicleGroup' | 'department' | 'section' | 'driver' | 'eventType'
 8    value: string
 9  }
10}
11
12export function parseUserSettings() {
13  // ...
14
15  const coachingDashboardFiltersRaw: Array<CoachingDashboardFilterType.ApiInput> =
16    settings.coaching_dashboard_filters
17
18  const coachingDashboardFilters = coachingDashboardFiltersRaw
19    .map((filter) => {
20      const parsedResult = apiCoachingDashboardFilterType.safeParse(filter.id)
21      if (!parsedResult.success) {
22        return null
23      }
24      const parsed: CoachingDashboardFilterType.ParsedEvent = {
25        id: parsedResult.data,
26        value: `${filter.default}`,
27      }
28      return parsed
29    })
30    .filter(Re.isNot(Re.isNil))
31  // ...
32}

And when we return the parsed values, instead of returning it directly, we can define a variable as parsed value type. With this, if the input data have some different values in the future, typescript would report it.

1const parsed: CoachingDashboardFilterType.ParsedEvent = {
2  id: parsedResult.data,
3  value: `${filter.default}`,
4}
5return parsed

Form

Not recommend to use onChange to update the form field value in react-hook-form

When we use the react-hook-form's Controller to

react-hook-form&#39;s onChange not support Typescript well
react-hook-form's onChange not support Typescript well

comments powered by Disqus