Skip to content

Commit

Permalink
Form Builder: Improve DnD performance (#923)
Browse files Browse the repository at this point in the history
  • Loading branch information
manzoorwanijk committed Jun 11, 2021
1 parent 4a726c0 commit 92d4a1b
Show file tree
Hide file tree
Showing 15 changed files with 71 additions and 33 deletions.
6 changes: 2 additions & 4 deletions packages/form-builder/src/FormBuilder.tsx
Expand Up @@ -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';
Expand Down Expand Up @@ -42,9 +42,7 @@ const FormBuilder: React.FC<FormBuilderProps> = ({ bodyClassName, containerClass

return (
<div {...droppableProps} className={className} ref={innerRef}>
{getSections().map((formSection, index) => (
<FormSection key={formSection.UUID} formSection={formSection} index={index} />
))}
<FormSections formSections={getSections()} />
{placeholder}
</div>
);
Expand Down
6 changes: 4 additions & 2 deletions 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';
Expand All @@ -9,7 +11,7 @@ import { useFormState } from '../state';

import type { FormElementProps } from '../types';

export const FormElement: React.FC<FormElementProps> = ({ element, index }) => {
export const FormElement = memo<FormElementProps>(({ element, index }) => {
const { isElementOpen } = useFormState();
const active = isElementOpen({ UUID: element.UUID });
const wrapperClass = classNames('ee-form-element__wrapper', active && 'ee-form-element__wrapper--active');
Expand All @@ -30,4 +32,4 @@ export const FormElement: React.FC<FormElementProps> = ({ element, index }) => {
}}
</Draggable>
);
};
}, getPropsAreEqual([['element'], ['index']]));
8 changes: 4 additions & 4 deletions 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<FormElementProps> = ({ element }) => {
export const FormElementInput = memo<FormElementProps>(({ element }) => {
const onChangeValue = useUpdateElement(element);

const props = useMemo(() => {
Expand Down Expand Up @@ -82,4 +82,4 @@ export const FormElementInput: React.FC<FormElementProps> = ({ element }) => {
{element.helpText && <FormHelperText>{element.helpText}</FormHelperText>}
</FormControl>
);
};
}, getPropsAreEqual([['element']]));
7 changes: 4 additions & 3 deletions 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<FormElementToolbarProps> = ({ element, dragHandleProps }) => {
export const FormElementToolbar = memo<FormElementToolbarProps>(({ element, dragHandleProps }) => {
const { copyElement, deleteElement, isElementOpen, toggleOpenElement } = useFormState();
const { UUID } = element;
const active = isElementOpen({ UUID });
Expand Down Expand Up @@ -72,4 +73,4 @@ export const FormElementToolbar: React.FC<FormElementToolbarProps> = ({ element,
</div>
</div>
);
};
}, getPropsAreEqual([['element']]));
@@ -1,14 +1,17 @@
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';
import { Styles } from './Styles';
import { Validation } from './Validation';
import { useFormState } from '../../state';

export const FormElementTabs: React.FC<FormElementProps> = ({ element }) => {
export const FormElementTabs = memo<FormElementProps>(({ element }) => {
const { isElementOpen } = useFormState();
return (
<Collapsible show={isElementOpen({ UUID: element.UUID })}>
Expand Down Expand Up @@ -48,4 +51,4 @@ export const FormElementTabs: React.FC<FormElementProps> = ({ element }) => {
</Tabs>
</Collapsible>
);
};
}, getPropsAreEqual([['element']]));
6 changes: 4 additions & 2 deletions 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';
Expand All @@ -10,7 +12,7 @@ import { useFormState } from '../state';

import type { FormSectionProps } from '../types';

export const FormSection: React.FC<FormSectionProps> = ({ formSection, index }) => {
export const FormSection = memo<FormSectionProps>(({ formSection, index }) => {
const { isElementOpen } = useFormState();

const { UUID } = formSection;
Expand All @@ -36,4 +38,4 @@ export const FormSection: React.FC<FormSectionProps> = ({ formSection, index })
}}
</Draggable>
);
};
}, getPropsAreEqual([['formSection'], ['index']]));
6 changes: 4 additions & 2 deletions 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<FormSectionProps> = ({ formSection }) => {
export const FormSectionElements = memo<FormSectionProps>(({ formSection }) => {
const { getElements } = useFormState();

return (
Expand All @@ -25,4 +27,4 @@ export const FormSectionElements: React.FC<FormSectionProps> = ({ formSection })
}}
</Droppable>
);
};
}, getPropsAreEqual([['formSection']]));
7 changes: 4 additions & 3 deletions 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<FormSectionToolbarProps> = ({ formSection, dragHandleProps }) => {
export const FormSectionToolbar = memo<FormSectionToolbarProps>(({ formSection, dragHandleProps }) => {
const { copySection, deleteSection, isElementOpen, toggleOpenElement } = useFormState();

const { UUID } = formSection;
Expand Down Expand Up @@ -72,4 +73,4 @@ export const FormSectionToolbar: React.FC<FormSectionToolbarProps> = ({ formSect
</div>
</div>
);
};
}, getPropsAreEqual([['formSection']]));
21 changes: 21 additions & 0 deletions 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<TFormSection>;
};

export const FormSections: React.FC<FormSectionsProps> = memo(({ formSections }) => {
return (
<>
{formSections.map((formSection, index) => (
<FormSection key={formSection.UUID} formSection={formSection} index={index} />
))}
</>
);
}, getPropsAreEqual([['formSections']]));
@@ -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<FormSectionProps> = ({ formSection }) => {
export const FormSectionTabs = memo<FormSectionProps>(({ formSection }) => {
const { isElementOpen } = useFormState();
return (
<Collapsible show={isElementOpen({ UUID: formSection.UUID })}>
Expand Down Expand Up @@ -40,4 +43,4 @@ export const FormSectionTabs: React.FC<FormSectionProps> = ({ formSection }) =>
</Tabs>
</Collapsible>
);
};
}, getPropsAreEqual([['formSection']]));
2 changes: 1 addition & 1 deletion packages/form-builder/src/FormSection/index.ts
@@ -1 +1 @@
export * from './FormSection';
export * from './FormSections';
4 changes: 2 additions & 2 deletions packages/form-builder/src/state/useFormStateManager.ts
Expand Up @@ -22,7 +22,7 @@ export const useFormStateManager: FormStateManagerHook = (props) => {
const getSections = useCallback<FSM['getSections']>(() => {
const sections = Object.values(state.sections).filter(isNotSharedOrDefault);
return sortByOrder(sections);
}, [state]);
}, [state.sections]);

const getElements = useCallback<FSM['getElements']>(
({ sectionId }) => {
Expand All @@ -32,7 +32,7 @@ export const useFormStateManager: FormStateManagerHook = (props) => {
}
return sortByOrder(elements);
},
[state]
[state.elements]
);

const addSection = useCallback<FSM['addSection']>(({ section, afterUuid }) => {
Expand Down
1 change: 1 addition & 0 deletions packages/form-builder/src/state/useFormStateReducer.ts
Expand Up @@ -20,6 +20,7 @@ export const initialState: FormState = {
deletedElements: [],
deletedSections: [],
isDirty: false,
openElement: '',
};

export const useFormStateReducer = (initializer: StateInitializer): FormStateReducer => {
Expand Down
14 changes: 9 additions & 5 deletions packages/utils/src/memo/getPropsAreEqual.ts
@@ -1,21 +1,27 @@
import { path, Path } from 'ramda';

import { isEqualJson } from '../misc';
import type { AnyObject } from '../types';

type PropsAreEqual<P extends AnyObject> = (
export type PropsAreEqual<P extends AnyObject> = (
prevProps: Readonly<React.PropsWithChildren<P>>,
nextProps: Readonly<React.PropsWithChildren<P>>
) => boolean;

/**
* Generates the comparison function that can be used as second argument to React.memo()
*/
const getPropsAreEqual = <P extends AnyObject>(...paths: Array<Path>): PropsAreEqual<P> => {
export const getPropsAreEqual = <P extends AnyObject>(paths: Array<Path>, compareAsJson = true): PropsAreEqual<P> => {
const propsAreEqual: PropsAreEqual<P> = (prevProps, nextProps): boolean => {
for (const pathToValue of paths) {
const prevValue = path<any>(pathToValue, prevProps);
const nextValue = path<any>(pathToValue, nextProps);

if (prevValue !== nextValue) {
if (compareAsJson) {
if (!isEqualJson(prevValue, nextValue)) {
return false;
}
} else if (prevValue !== nextValue) {
return false;
}
}
Expand All @@ -24,5 +30,3 @@ const getPropsAreEqual = <P extends AnyObject>(...paths: Array<Path>): PropsAreE

return propsAreEqual;
};

export default getPropsAreEqual;
2 changes: 1 addition & 1 deletion 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';

0 comments on commit 92d4a1b

Please sign in to comment.