diff --git a/packages/form-builder/src/FormBuilder.tsx b/packages/form-builder/src/FormBuilder.tsx index b303f51bb..ceb703d1d 100644 --- a/packages/form-builder/src/FormBuilder.tsx +++ b/packages/form-builder/src/FormBuilder.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { DragDropContext, Droppable } from '@eventespresso/adapters'; import { Container } from '@eventespresso/ui-components'; -import { FormSection } from './FormSection'; +import { FormSections } from './FormSection'; import { withFormState } from './context'; import { useFormState, useHandleDnD } from './state'; import { SECTIONS_DROPPABLE_ID } from './constants'; @@ -42,9 +42,7 @@ const FormBuilder: React.FC = ({ bodyClassName, containerClass return (
- {getSections().map((formSection, index) => ( - - ))} + {placeholder}
); diff --git a/packages/form-builder/src/FormElement/FormElement.tsx b/packages/form-builder/src/FormElement/FormElement.tsx index 0ad00aa56..e316b1378 100644 --- a/packages/form-builder/src/FormElement/FormElement.tsx +++ b/packages/form-builder/src/FormElement/FormElement.tsx @@ -1,6 +1,8 @@ +import { memo } from 'react'; import classNames from 'classnames'; import { Draggable } from '@eventespresso/adapters'; +import { getPropsAreEqual } from '@eventespresso/utils'; import { FormElementInput } from './FormElementInput'; import { FormElementToolbar } from './FormElementToolbar'; @@ -9,7 +11,7 @@ import { useFormState } from '../state'; import type { FormElementProps } from '../types'; -export const FormElement: React.FC = ({ element, index }) => { +export const FormElement = memo(({ element, index }) => { const { isElementOpen } = useFormState(); const active = isElementOpen({ UUID: element.UUID }); const wrapperClass = classNames('ee-form-element__wrapper', active && 'ee-form-element__wrapper--active'); @@ -30,4 +32,4 @@ export const FormElement: React.FC = ({ element, index }) => { }} ); -}; +}, getPropsAreEqual([['element'], ['index']])); diff --git a/packages/form-builder/src/FormElement/FormElementInput.tsx b/packages/form-builder/src/FormElement/FormElementInput.tsx index 9f1d5244a..d1d5959c5 100644 --- a/packages/form-builder/src/FormElement/FormElementInput.tsx +++ b/packages/form-builder/src/FormElement/FormElementInput.tsx @@ -1,13 +1,13 @@ -import { useMemo } from 'react'; +import { memo, useMemo } from 'react'; import { FormControl, FormHelperText } from '@eventespresso/adapters'; -import type { AnyObject } from '@eventespresso/utils'; +import { AnyObject, getPropsAreEqual } from '@eventespresso/utils'; import { MappedElement } from './MappedElement'; import type { FormElementProps } from '../types'; import { useUpdateElement } from './useUpdateElement'; -export const FormElementInput: React.FC = ({ element }) => { +export const FormElementInput = memo(({ element }) => { const onChangeValue = useUpdateElement(element); const props = useMemo(() => { @@ -82,4 +82,4 @@ export const FormElementInput: React.FC = ({ element }) => { {element.helpText && {element.helpText}} ); -}; +}, getPropsAreEqual([['element']])); diff --git a/packages/form-builder/src/FormElement/FormElementToolbar.tsx b/packages/form-builder/src/FormElement/FormElementToolbar.tsx index 74b792ce8..0bbbe0e16 100644 --- a/packages/form-builder/src/FormElement/FormElementToolbar.tsx +++ b/packages/form-builder/src/FormElement/FormElementToolbar.tsx @@ -1,15 +1,16 @@ -import { useCallback, useMemo } from 'react'; +import { memo, useCallback, useMemo } from 'react'; import { __ } from '@eventespresso/i18n'; import { Copy, DragHandle, SettingsOutlined, Trash } from '@eventespresso/icons'; import { IconButton, ButtonProps, ConfirmDelete } from '@eventespresso/ui-components'; +import { getPropsAreEqual } from '@eventespresso/utils'; import { ELEMENT_BLOCKS_INDEXED } from '../constants'; import { useFormState } from '../state'; import type { FormElementToolbarProps } from '../types'; -export const FormElementToolbar: React.FC = ({ element, dragHandleProps }) => { +export const FormElementToolbar = memo(({ element, dragHandleProps }) => { const { copyElement, deleteElement, isElementOpen, toggleOpenElement } = useFormState(); const { UUID } = element; const active = isElementOpen({ UUID }); @@ -72,4 +73,4 @@ export const FormElementToolbar: React.FC = ({ element, ); -}; +}, getPropsAreEqual([['element']])); diff --git a/packages/form-builder/src/FormElement/Tabs/FormElementTabs.tsx b/packages/form-builder/src/FormElement/Tabs/FormElementTabs.tsx index 907473f12..f8b888f42 100644 --- a/packages/form-builder/src/FormElement/Tabs/FormElementTabs.tsx +++ b/packages/form-builder/src/FormElement/Tabs/FormElementTabs.tsx @@ -1,6 +1,9 @@ +import { memo } from 'react'; + import { __ } from '@eventespresso/i18n'; import { Check, CheckList, Palette, SettingsOutlined } from '@eventespresso/icons'; import { Collapsible, Tabs, TabList, TabPanels, Tab, TabPanel } from '@eventespresso/ui-components'; +import { getPropsAreEqual } from '@eventespresso/utils'; import type { FormElementProps } from '../../types'; import { Settings } from './Settings'; @@ -8,7 +11,7 @@ import { Styles } from './Styles'; import { Validation } from './Validation'; import { useFormState } from '../../state'; -export const FormElementTabs: React.FC = ({ element }) => { +export const FormElementTabs = memo(({ element }) => { const { isElementOpen } = useFormState(); return ( @@ -48,4 +51,4 @@ export const FormElementTabs: React.FC = ({ element }) => { ); -}; +}, getPropsAreEqual([['element']])); diff --git a/packages/form-builder/src/FormSection/FormSection.tsx b/packages/form-builder/src/FormSection/FormSection.tsx index 596cf9617..2cec4ef77 100644 --- a/packages/form-builder/src/FormSection/FormSection.tsx +++ b/packages/form-builder/src/FormSection/FormSection.tsx @@ -1,6 +1,8 @@ +import { memo } from 'react'; import classNames from 'classnames'; import { Draggable } from '@eventespresso/adapters'; +import { getPropsAreEqual } from '@eventespresso/utils'; import { AddFormElementPopover } from './AddFormElementPopover'; import { FormSectionToolbar } from './FormSectionToolbar'; @@ -10,7 +12,7 @@ import { useFormState } from '../state'; import type { FormSectionProps } from '../types'; -export const FormSection: React.FC = ({ formSection, index }) => { +export const FormSection = memo(({ formSection, index }) => { const { isElementOpen } = useFormState(); const { UUID } = formSection; @@ -36,4 +38,4 @@ export const FormSection: React.FC = ({ formSection, index }) }} ); -}; +}, getPropsAreEqual([['formSection'], ['index']])); diff --git a/packages/form-builder/src/FormSection/FormSectionElements.tsx b/packages/form-builder/src/FormSection/FormSectionElements.tsx index 83c7fa512..84ff9c793 100644 --- a/packages/form-builder/src/FormSection/FormSectionElements.tsx +++ b/packages/form-builder/src/FormSection/FormSectionElements.tsx @@ -1,12 +1,14 @@ +import { memo } from 'react'; import classNames from 'classnames'; import { Droppable } from '@eventespresso/adapters'; +import { getPropsAreEqual } from '@eventespresso/utils'; import { useFormState } from '../state'; import type { FormSectionProps } from '../types'; import { FormElement } from '../FormElement'; -export const FormSectionElements: React.FC = ({ formSection }) => { +export const FormSectionElements = memo(({ formSection }) => { const { getElements } = useFormState(); return ( @@ -25,4 +27,4 @@ export const FormSectionElements: React.FC = ({ formSection }) }} ); -}; +}, getPropsAreEqual([['formSection']])); diff --git a/packages/form-builder/src/FormSection/FormSectionToolbar.tsx b/packages/form-builder/src/FormSection/FormSectionToolbar.tsx index 6c1fb4029..efe9017f6 100644 --- a/packages/form-builder/src/FormSection/FormSectionToolbar.tsx +++ b/packages/form-builder/src/FormSection/FormSectionToolbar.tsx @@ -1,15 +1,16 @@ -import { useCallback, useMemo } from 'react'; +import { useCallback, useMemo, memo } from 'react'; import { __ } from '@eventespresso/i18n'; import { Copy, DragHandle, SettingsOutlined, Trash } from '@eventespresso/icons'; import { IconButton, ButtonProps, ConfirmDelete } from '@eventespresso/ui-components'; +import { getPropsAreEqual } from '@eventespresso/utils'; import { useFormState } from '../state'; import { SaveSection } from './SaveSection'; import type { FormSectionToolbarProps } from '../types'; -export const FormSectionToolbar: React.FC = ({ formSection, dragHandleProps }) => { +export const FormSectionToolbar = memo(({ formSection, dragHandleProps }) => { const { copySection, deleteSection, isElementOpen, toggleOpenElement } = useFormState(); const { UUID } = formSection; @@ -72,4 +73,4 @@ export const FormSectionToolbar: React.FC = ({ formSect ); -}; +}, getPropsAreEqual([['formSection']])); diff --git a/packages/form-builder/src/FormSection/FormSections.tsx b/packages/form-builder/src/FormSection/FormSections.tsx new file mode 100644 index 000000000..2bb38d26b --- /dev/null +++ b/packages/form-builder/src/FormSection/FormSections.tsx @@ -0,0 +1,21 @@ +import { memo } from 'react'; + +import { getPropsAreEqual } from '@eventespresso/utils'; + +import { FormSection } from './FormSection'; + +import type { FormSection as TFormSection } from '../types'; + +export type FormSectionsProps = { + formSections: Array; +}; + +export const FormSections: React.FC = memo(({ formSections }) => { + return ( + <> + {formSections.map((formSection, index) => ( + + ))} + + ); +}, getPropsAreEqual([['formSections']])); diff --git a/packages/form-builder/src/FormSection/Tabs/FormSectionTabs.tsx b/packages/form-builder/src/FormSection/Tabs/FormSectionTabs.tsx index 17b9f1ada..eb9c42fc6 100644 --- a/packages/form-builder/src/FormSection/Tabs/FormSectionTabs.tsx +++ b/packages/form-builder/src/FormSection/Tabs/FormSectionTabs.tsx @@ -1,13 +1,16 @@ +import { memo } from 'react'; + import { __ } from '@eventespresso/i18n'; import { CheckList, Palette, SettingsOutlined } from '@eventespresso/icons'; import { Collapsible, Tabs, TabList, TabPanels, Tab, TabPanel } from '@eventespresso/ui-components'; +import { getPropsAreEqual } from '@eventespresso/utils'; import { useFormState } from '../../state'; import { Styles } from './Styles'; import type { FormSectionProps } from '../../types'; import { Settings } from './Settings'; -export const FormSectionTabs: React.FC = ({ formSection }) => { +export const FormSectionTabs = memo(({ formSection }) => { const { isElementOpen } = useFormState(); return ( @@ -40,4 +43,4 @@ export const FormSectionTabs: React.FC = ({ formSection }) => ); -}; +}, getPropsAreEqual([['formSection']])); diff --git a/packages/form-builder/src/FormSection/index.ts b/packages/form-builder/src/FormSection/index.ts index 528f3e76a..c99da194a 100644 --- a/packages/form-builder/src/FormSection/index.ts +++ b/packages/form-builder/src/FormSection/index.ts @@ -1 +1 @@ -export * from './FormSection'; +export * from './FormSections'; diff --git a/packages/form-builder/src/state/useFormStateManager.ts b/packages/form-builder/src/state/useFormStateManager.ts index cae7c6c37..78e787645 100644 --- a/packages/form-builder/src/state/useFormStateManager.ts +++ b/packages/form-builder/src/state/useFormStateManager.ts @@ -22,7 +22,7 @@ export const useFormStateManager: FormStateManagerHook = (props) => { const getSections = useCallback(() => { const sections = Object.values(state.sections).filter(isNotSharedOrDefault); return sortByOrder(sections); - }, [state]); + }, [state.sections]); const getElements = useCallback( ({ sectionId }) => { @@ -32,7 +32,7 @@ export const useFormStateManager: FormStateManagerHook = (props) => { } return sortByOrder(elements); }, - [state] + [state.elements] ); const addSection = useCallback(({ section, afterUuid }) => { diff --git a/packages/form-builder/src/state/useFormStateReducer.ts b/packages/form-builder/src/state/useFormStateReducer.ts index a7fe2c767..d220a6a55 100644 --- a/packages/form-builder/src/state/useFormStateReducer.ts +++ b/packages/form-builder/src/state/useFormStateReducer.ts @@ -20,6 +20,7 @@ export const initialState: FormState = { deletedElements: [], deletedSections: [], isDirty: false, + openElement: '', }; export const useFormStateReducer = (initializer: StateInitializer): FormStateReducer => { diff --git a/packages/utils/src/memo/getPropsAreEqual.ts b/packages/utils/src/memo/getPropsAreEqual.ts index a581b8a4c..987d5a04a 100644 --- a/packages/utils/src/memo/getPropsAreEqual.ts +++ b/packages/utils/src/memo/getPropsAreEqual.ts @@ -1,7 +1,9 @@ import { path, Path } from 'ramda'; + +import { isEqualJson } from '../misc'; import type { AnyObject } from '../types'; -type PropsAreEqual

= ( +export type PropsAreEqual

= ( prevProps: Readonly>, nextProps: Readonly> ) => boolean; @@ -9,13 +11,17 @@ type PropsAreEqual

= ( /** * Generates the comparison function that can be used as second argument to React.memo() */ -const getPropsAreEqual =

(...paths: Array): PropsAreEqual

=> { +export const getPropsAreEqual =

(paths: Array, compareAsJson = true): PropsAreEqual

=> { const propsAreEqual: PropsAreEqual

= (prevProps, nextProps): boolean => { for (const pathToValue of paths) { const prevValue = path(pathToValue, prevProps); const nextValue = path(pathToValue, nextProps); - if (prevValue !== nextValue) { + if (compareAsJson) { + if (!isEqualJson(prevValue, nextValue)) { + return false; + } + } else if (prevValue !== nextValue) { return false; } } @@ -24,5 +30,3 @@ const getPropsAreEqual =

(...paths: Array): PropsAreE return propsAreEqual; }; - -export default getPropsAreEqual; diff --git a/packages/utils/src/memo/index.ts b/packages/utils/src/memo/index.ts index 9d241a497..89587165a 100644 --- a/packages/utils/src/memo/index.ts +++ b/packages/utils/src/memo/index.ts @@ -1,2 +1,2 @@ export { default as entityListCacheIdString } from './entityListCacheIdString'; -export { default as getPropsAreEqual } from './getPropsAreEqual'; +export * from './getPropsAreEqual';