From 9f94ecb8340101078ab90dca33cf4a06775a5915 Mon Sep 17 00:00:00 2001 From: foochifa Date: Tue, 25 Apr 2023 11:41:01 +0800 Subject: [PATCH 1/3] chore: export props from hooks for in FieldRow props --- .../BuilderAndDesignContent/FieldRow/index.ts | 2 +- .../create/builder-and-design/BuilderAndDesignContext.ts | 2 +- .../CreatePageSidebarContext/CreatePageSidebarContext.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/index.ts b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/index.ts index 97cab10d0b..89a5a1aef5 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/index.ts +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/index.ts @@ -1 +1 @@ -export { FieldRowContainer as default } from './FieldRowContainer' +export { MemoizedFieldRow as default } from './FieldRowContainer' diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContext.ts b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContext.ts index 4aa9a64efb..ef8e303967 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContext.ts +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContext.ts @@ -1,7 +1,7 @@ import { createContext, useContext } from 'react' import { UseDisclosureReturn } from '@chakra-ui/react' -type BuilderAndDesignContextProps = { +export type BuilderAndDesignContextProps = { deleteFieldModalDisclosure: UseDisclosureReturn } diff --git a/frontend/src/features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext.tsx b/frontend/src/features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext.tsx index 1e93ae1634..5514c00307 100644 --- a/frontend/src/features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext.tsx +++ b/frontend/src/features/admin-form/create/common/CreatePageSidebarContext/CreatePageSidebarContext.tsx @@ -35,7 +35,7 @@ export enum DrawerTabs { EndPage, } -type CreatePageSidebarContextProps = { +export type CreatePageSidebarContextProps = { activeTab: DrawerTabs | null pendingTab?: DrawerTabs | null movePendingToActiveTab: () => void From 7de475466cb6a591813a9ed4b896e2755b349329 Mon Sep 17 00:00:00 2001 From: foochifa Date: Tue, 25 Apr 2023 11:41:31 +0800 Subject: [PATCH 2/3] feat: shift hooks from fieldRow to BuilderFields --- .../BuilderAndDesignContent/BuilderFields.tsx | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderFields.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderFields.tsx index 66eb4da424..d156b78ebd 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderFields.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/BuilderFields.tsx @@ -1,8 +1,17 @@ import { AdminFormDto } from '~shared/types/form' +import { useCreatePageSidebar } from '~features/admin-form/create/common/CreatePageSidebarContext' import { augmentWithQuestionNo } from '~features/form/utils' import { FieldIdSet } from '~features/logic/types' +import { useBuilderAndDesignContext } from '../BuilderAndDesignContext' +import { PENDING_CREATE_FIELD_ID } from '../constants' +import { + FieldBuilderState, + stateDataSelector, + useFieldBuilderStore, +} from '../useFieldBuilderStore' + import FieldRow from './FieldRow' interface BuilderFieldsProps { @@ -17,6 +26,13 @@ export const BuilderFields = ({ isDraggingOver, }: BuilderFieldsProps) => { const fieldsWithQuestionNos = augmentWithQuestionNo(fields) + const stateData = useFieldBuilderStore(stateDataSelector) + + const { handleBuilderClick } = useCreatePageSidebar() + const { + deleteFieldModalDisclosure: { onOpen: onDeleteModalOpen }, + } = useBuilderAndDesignContext() + return ( <> {fieldsWithQuestionNos.map((f, i) => ( @@ -26,6 +42,16 @@ export const BuilderFields = ({ field={f} isHiddenByLogic={!visibleFieldIds.has(f._id)} isDraggingOver={isDraggingOver} + isActive={ + stateData.state === FieldBuilderState.EditingField + ? f._id === stateData.field._id + : stateData.state === FieldBuilderState.CreatingField + ? f._id === PENDING_CREATE_FIELD_ID + : false + } + fieldBuilderState={stateData.state} + handleBuilderClick={handleBuilderClick} + onDeleteModalOpen={onDeleteModalOpen} /> ))} From 7a2c480e44f1d9f6748cd8d49fceeb297227cad2 Mon Sep 17 00:00:00 2001 From: foochifa Date: Tue, 25 Apr 2023 11:41:57 +0800 Subject: [PATCH 3/3] feat: memoize FieldRow with deep equality check --- .../FieldRow/FieldRowContainer.tsx | 69 ++++++++----------- 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx index 9c84d78e4d..56c960588b 100644 --- a/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx +++ b/frontend/src/features/admin-form/create/builder-and-design/BuilderAndDesignContent/FieldRow/FieldRowContainer.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef } from 'react' +import { memo, useCallback, useEffect, useMemo, useRef } from 'react' import { Draggable } from 'react-beautiful-dnd' import { FormProvider, useForm } from 'react-hook-form' import { BiCog, BiDuplicate, BiGridHorizontal, BiTrash } from 'react-icons/bi' @@ -12,7 +12,7 @@ import { Flex, Icon, } from '@chakra-ui/react' -import { times } from 'lodash' +import { isEqual, times } from 'lodash' import { FormColorTheme } from '~shared/types' import { BasicField, FormFieldDto } from '~shared/types/field' @@ -47,7 +47,7 @@ import { MobileFieldInput } from '~templates/Field/Mobile' import { createTableRow } from '~templates/Field/Table/utils/createRow' import { adminFormKeys } from '~features/admin-form/common/queries' -import { useCreatePageSidebar } from '~features/admin-form/create/common/CreatePageSidebarContext' +import { CreatePageSidebarContextProps } from '~features/admin-form/create/common' import { augmentWithMyInfoDisplayValue, extractPreviewValue, @@ -55,8 +55,7 @@ import { isMyInfo, } from '~features/myinfo/utils' -import { useBuilderAndDesignContext } from '../../BuilderAndDesignContext' -import { PENDING_CREATE_FIELD_ID } from '../../constants' +import { BuilderAndDesignContextProps } from '../../BuilderAndDesignContext' import { useDeleteFormField } from '../../mutations/useDeleteFormField' import { useDuplicateFormField } from '../../mutations/useDuplicateFormField' import { useCreateTabForm } from '../../useCreateTabForm' @@ -69,7 +68,6 @@ import { isDirtySelector, useDirtyFieldStore } from '../../useDirtyFieldStore' import { FieldBuilderState, setToInactiveSelector, - stateDataSelector, updateEditStateSelector, useFieldBuilderStore, } from '../../useFieldBuilderStore' @@ -84,35 +82,33 @@ export interface FieldRowContainerProps { index: number isHiddenByLogic: boolean isDraggingOver: boolean + isActive: boolean + fieldBuilderState: FieldBuilderState + handleBuilderClick: CreatePageSidebarContextProps['handleBuilderClick'] + onDeleteModalOpen: BuilderAndDesignContextProps['deleteFieldModalDisclosure']['onOpen'] } -export const FieldRowContainer = ({ +const FieldRowContainer = ({ field, index, isHiddenByLogic, isDraggingOver, + isActive, + fieldBuilderState, + handleBuilderClick, + onDeleteModalOpen, }: FieldRowContainerProps): JSX.Element => { const isMobile = useIsMobile() const { data: form } = useCreateTabForm() const numFormFieldMutations = useIsMutating(adminFormKeys.base) - const { stateData, setToInactive, updateEditState } = useFieldBuilderStore( - useCallback( - (state) => ({ - stateData: stateDataSelector(state), - setToInactive: setToInactiveSelector(state), - updateEditState: updateEditStateSelector(state), - }), - [], - ), - ) + const setToInactive = useFieldBuilderStore(setToInactiveSelector) + const updateEditState = useFieldBuilderStore(updateEditStateSelector) const isDirty = useDirtyFieldStore(isDirtySelector) const toast = useToast({ status: 'danger', isClosable: true }) const setDesignState = useDesignStore(setStateSelector) - const { handleBuilderClick } = useCreatePageSidebar() - const { duplicateFieldMutation } = useDuplicateFormField() const { deleteFieldMutation } = useDeleteFormField() @@ -141,19 +137,6 @@ export const FieldRowContainer = ({ defaultValues: defaultFieldValues, }) - const isActive = useMemo(() => { - if (stateData.state === FieldBuilderState.EditingField) { - return field._id === stateData.field._id - } else if (stateData.state === FieldBuilderState.CreatingField) { - return field._id === PENDING_CREATE_FIELD_ID - } - return false - }, [stateData, field]) - - const { - deleteFieldModalDisclosure: { onOpen: onDeleteModalOpen }, - } = useBuilderAndDesignContext() - const ref = useRef(null) useEffect(() => { if (isActive) { @@ -208,7 +191,7 @@ export const FieldRowContainer = ({ const handleDuplicateClick = useCallback(() => { if (!form) return // Duplicate button should be hidden if field is not yet created, but guard here just in case - if (stateData.state === FieldBuilderState.CreatingField) return + if (fieldBuilderState === FieldBuilderState.CreatingField) return // Disallow duplicating attachment fields if after the dupe, the filesize exceeds the limit if (field.fieldType === BasicField.Attachment) { const existingAttachmentsSize = form.form_fields.reduce( @@ -230,15 +213,15 @@ export const FieldRowContainer = ({ } } duplicateFieldMutation.mutate(field._id) - }, [stateData.state, field, duplicateFieldMutation, form, toast]) + }, [fieldBuilderState, field, duplicateFieldMutation, form, toast]) const handleDeleteClick = useCallback(() => { - if (stateData.state === FieldBuilderState.CreatingField) { + if (fieldBuilderState === FieldBuilderState.CreatingField) { setToInactive() - } else if (stateData.state === FieldBuilderState.EditingField) { + } else if (fieldBuilderState === FieldBuilderState.EditingField) { onDeleteModalOpen() } - }, [setToInactive, stateData.state, onDeleteModalOpen]) + }, [setToInactive, fieldBuilderState, onDeleteModalOpen]) const isAnyMutationLoading = useMemo( () => duplicateFieldMutation.isLoading || deleteFieldMutation.isLoading, @@ -250,9 +233,9 @@ export const FieldRowContainer = ({ !isActive || isDirty || !!numFormFieldMutations || - stateData.state === FieldBuilderState.CreatingField + fieldBuilderState === FieldBuilderState.CreatingField ) - }, [isActive, isDirty, numFormFieldMutations, stateData.state]) + }, [isActive, isDirty, numFormFieldMutations, fieldBuilderState]) return ( - {stateData.state === FieldBuilderState.EditingField && + {fieldBuilderState === FieldBuilderState.EditingField && !isDragDisabled ? ( ) : ( @@ -377,7 +360,7 @@ export const FieldRowContainer = ({ ) : null} { // Fields which are not yet created cannot be duplicated - stateData.state !== FieldBuilderState.CreatingField && ( + fieldBuilderState !== FieldBuilderState.CreatingField && ( + isEqual(prevProps, newProps), +) + type FieldRowProps = { field: FormFieldDto colorTheme?: FormColorTheme