Skip to content

Commit

Permalink
✨ [#4798] Prevent useConfirm from rendering components
Browse files Browse the repository at this point in the history
Rendering a component inside a hook has some serious performance impacts. To prevent this, the useConfirm now returns the needed props and a reference to the confirmationModal component.

For more info: #4814 (comment)
  • Loading branch information
robinmolen committed Nov 14, 2024
1 parent f0b5ea0 commit 115e00e
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 101 deletions.
21 changes: 11 additions & 10 deletions src/openforms/js/components/admin/form_design/form-creation-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -1072,14 +1072,7 @@ const FormCreationForm = ({formUuid, formUrl, formHistoryUrl, outgoingRequestsUr
}
}, [loading]);

const [UserVariableConfirmationModal, confirmUserVariableChange] = useConfirm(
intl.formatMessage({
description:
'Changing user variable data type and transforming initial value confirmation message',
defaultMessage:
'Changing the data type requires the initial value to be changed. This will reset the initial value back to the empty value. Are you sure that you want to do this?',
})
);
const {ConfirmationModal, confirmationModalProps, openConfirmationModal} = useConfirm();

/**
* Functions for handling events
Expand Down Expand Up @@ -1265,7 +1258,7 @@ const FormCreationForm = ({formUuid, formUrl, formHistoryUrl, outgoingRequestsUr
}

// Check if the dataType change is intentional.
if (propertyName === 'dataType' && !(await confirmUserVariableChange())) {
if (propertyName === 'dataType' && !(await openConfirmationModal())) {
return;
}

Expand Down Expand Up @@ -1567,7 +1560,15 @@ const FormCreationForm = ({formUuid, formUrl, formHistoryUrl, outgoingRequestsUr
</Tabs>
</FormContext.Provider>

<UserVariableConfirmationModal />
<ConfirmationModal
{...confirmationModalProps}
message={
<FormattedMessage
description="Changing user variable data type and transforming initial value confirmation message"
defaultMessage="Changing the data type requires the initial value to be changed. This will reset the initial value back to the empty value. Are you sure that you want to do this?"
/>
}
/>
<FormSubmit onSubmit={onSubmit} displayActions={!state.newForm} />
</ValidationErrorsProvider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,16 @@ const ObjectsApiOptionsFormFields = ({name, apiGroupChoices}) => {
description: 'Objects API registration backend: v2 switch warning message',
});

const [ConfirmationModalV1, confirmUsingV1] = useConfirm(v1SwitchMessage);
const [ConfirmationModalV2, confirmUsingV2] = useConfirm(v2SwitchMessage);
const {
ConfirmationModal: ConfirmationModalV1,
confirmationModalProps: confirmationModalV1Props,
openConfirmationModal: confirmUsingV1,
} = useConfirm();
const {
ConfirmationModal: ConfirmationModalV2,
confirmationModalProps: confirmationModalV2Props,
openConfirmationModal: confirmUsingV2,
} = useConfirm();

const changeVersion = async tabIndex => {
const newVersion = tabIndex + 1;
Expand Down Expand Up @@ -100,8 +108,8 @@ const ObjectsApiOptionsFormFields = ({name, apiGroupChoices}) => {
<V2ConfigFields apiGroupChoices={apiGroupChoices} />
</TabPanel>
</Tabs>
<ConfirmationModalV1 />
<ConfirmationModalV2 />
<ConfirmationModalV1 {...confirmationModalV1Props} message={v1SwitchMessage} />
<ConfirmationModalV2 {...confirmationModalV2Props} message={v2SwitchMessage} />
</ValidationErrorsProvider>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useFormikContext} from 'formik';
import PropTypes from 'prop-types';
import {FormattedMessage, useIntl} from 'react-intl';
import {FormattedMessage} from 'react-intl';

import useConfirm from 'components/admin/form_design/useConfirm';
import Fieldset from 'components/admin/forms/Fieldset';
Expand Down Expand Up @@ -30,27 +30,21 @@ const onApiGroupChange = prevValues => ({
});

const V2ConfigFields = ({apiGroupChoices}) => {
const intl = useIntl();
const {
values: {variablesMapping = []},
setFieldValue,
} = useFormikContext();

const [ApiGroupConfirmationModal, confirmApiGroupChange] = useConfirm(
intl.formatMessage({
description: 'Objects API registration options: warning message when changing the api group',
defaultMessage: `Changing the api group will remove the existing variables mapping.
Are you sure you want to continue?`,
})
);
const [ObjectTypeConfirmationModal, confirmObjectTypeChange] = useConfirm(
intl.formatMessage({
description:
'Objects API registration options: warning message when changing the object type',
defaultMessage: `Changing the objecttype will remove the existing variables mapping.
Are you sure you want to continue?`,
})
);
const {
ConfirmationModal: ApiGroupConfirmationModal,
confirmationModalProps: apiGroupConfirmationModalProps,
openConfirmationModal: openApiGroupConfirmation,
} = useConfirm();
const {
ConfirmationModal: ObjectTypeConfirmationModal,
confirmationModalProps: objectTypeConfirmationModalProps,
openConfirmationModal: openObjectTypeConfirmation,
} = useConfirm();

return (
<>
Expand All @@ -59,7 +53,7 @@ const V2ConfigFields = ({apiGroupChoices}) => {
apiGroupChoices={apiGroupChoices}
onChangeCheck={async () => {
if (variablesMapping.length === 0) return true;
const confirmSwitch = await confirmApiGroupChange();
const confirmSwitch = await openApiGroupConfirmation();
if (!confirmSwitch) return false;
setFieldValue('variablesMapping', []);
return true;
Expand All @@ -77,7 +71,7 @@ const V2ConfigFields = ({apiGroupChoices}) => {
<ObjectTypeSelect
onChangeCheck={async () => {
if (variablesMapping.length === 0) return true;
const confirmSwitch = await confirmObjectTypeChange();
const confirmSwitch = await openObjectTypeConfirmation();
if (!confirmSwitch) return false;
setFieldValue('variablesMapping', []);
return true;
Expand Down Expand Up @@ -125,8 +119,26 @@ const V2ConfigFields = ({apiGroupChoices}) => {
<OrganisationRSIN />
</Fieldset>

<ApiGroupConfirmationModal />
<ObjectTypeConfirmationModal />
<ApiGroupConfirmationModal
{...apiGroupConfirmationModalProps}
message={
<FormattedMessage
description="Objects API registration options: warning message when changing the api group"
defaultMessage="Changing the api group will remove the existing variables mapping.
Are you sure you want to continue?"
/>
}
/>
<ObjectTypeConfirmationModal
{...objectTypeConfirmationModalProps}
message={
<FormattedMessage
description="Objects API registration options: warning message when changing the object type"
defaultMessage="Changing the objecttype will remove the existing variables mapping.
Are you sure you want to continue?"
/>
}
/>
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useFormikContext} from 'formik';
import PropTypes from 'prop-types';
import {FormattedMessage, useIntl} from 'react-intl';
import {FormattedMessage} from 'react-intl';
import {useAsync} from 'react-use';

import useConfirm from 'components/admin/form_design/useConfirm';
Expand All @@ -26,7 +26,6 @@ const getCatalogues = async apiGroupID => {
// Components

const BasicOptionsFieldset = ({apiGroupChoices}) => {
const intl = useIntl();
const {
values: {
zaaktype,
Expand All @@ -48,21 +47,15 @@ const BasicOptionsFieldset = ({apiGroupChoices}) => {
objecttypeVersion,
contentJson,
].some(v => !!v) || propertyMappings.length > 0;
const [ConfirmationModal, confirm] = useConfirm(
intl.formatMessage({
description: 'ZGW APIs registration options: warning message when changing the api group',
defaultMessage: `Changing the api group will clear the existing configuration.
Are you sure you want to continue?`,
})
);
const {ConfirmationModal, confirmationModalProps, openConfirmationModal} = useConfirm();

return (
<Fieldset>
<ZGWAPIGroup
apiGroupChoices={apiGroupChoices}
onChangeCheck={async () => {
if (!hasAnyFieldConfigured) return true;
return confirm();
return openConfirmationModal();
}}
/>

Expand All @@ -78,7 +71,16 @@ const BasicOptionsFieldset = ({apiGroupChoices}) => {
>
<CatalogiApiFields />
</ErrorBoundary>
<ConfirmationModal />
<ConfirmationModal
{...confirmationModalProps}
message={
<FormattedMessage
description="ZGW APIs registration options: warning message when changing the api group"
defaultMessage="Changing the api group will clear the existing configuration.
Are you sure you want to continue?"
/>
}
/>
</Fieldset>
);
};
Expand Down
65 changes: 38 additions & 27 deletions src/openforms/js/components/admin/form_design/useConfirm.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import PropTypes from 'prop-types';
import {useState} from 'react';
import {useIntl} from 'react-intl';

import ActionButton from 'components/admin/forms/ActionButton';
import {Modal} from 'components/admin/modals';

const useConfirm = (message, title = '') => {
const useConfirm = () => {
const [promise, setPromise] = useState(null);

const confirm = () =>
const openConfirmationModal = () =>
new Promise(resolve => {
setPromise({resolve});
});
Expand All @@ -26,32 +27,42 @@ const useConfirm = (message, title = '') => {
handleClose();
};

const ConfirmationModal = () => {
const intl = useIntl();
const confirmBtnText = intl.formatMessage({
description: 'Confirmation modal confirm button',
defaultMessage: 'Confirm',
});
const cancelBtnText = intl.formatMessage({
description: 'Confirmation modal cancel button',
defaultMessage: 'Cancel',
});
return (
<Modal
title={title}
isOpen={promise !== null}
contentModifiers={['confirmation']}
closeModal={handleCancel}
>
<p>{message}</p>
<div className="react-modal__actions">
<ActionButton text={confirmBtnText} className="default" onClick={handleConfirm} />
<ActionButton text={cancelBtnText} onClick={handleCancel} />
</div>
</Modal>
);
const confirmationModalProps = {
isOpen: promise !== null,
onConfirm: handleConfirm,
onCancel: handleCancel,
};
return [ConfirmationModal, confirm];

return {ConfirmationModal, confirmationModalProps, openConfirmationModal};
};

const ConfirmationModal = ({title, message, isOpen, onConfirm, onCancel}) => {
const intl = useIntl();
const confirmBtnText = intl.formatMessage({
description: 'Confirmation modal confirm button',
defaultMessage: 'Confirm',
});
const cancelBtnText = intl.formatMessage({
description: 'Confirmation modal cancel button',
defaultMessage: 'Cancel',
});
return (
<Modal title={title} isOpen={isOpen} contentModifiers={['confirmation']} closeModal={onCancel}>
<p>{message}</p>
<div className="react-modal__actions">
<ActionButton text={confirmBtnText} className="default" onClick={onConfirm} />
<ActionButton text={cancelBtnText} onClick={onCancel} />
</div>
</Modal>
);
};

ConfirmationModal.propTypes = {
title: PropTypes.node,
message: PropTypes.node.isRequired,
isOpen: PropTypes.bool.isRequired,
onConfirm: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
};

export default useConfirm;
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ import ActionButton from 'components/admin/forms/ActionButton';
import useConfirm from './useConfirm';

const ButtonWithUseConfirm = () => {
const [ConfirmationModal, confirm] = useConfirm(
'A sample confirmation message',
'The confirmation title'
);
const {ConfirmationModal, confirmationModalProps, openConfirmationModal} = useConfirm();
const [confirmationResult, setConfirmationResult] = useState(null);
return (
<div>
<ActionButton
text="Open confirmation modal"
onClick={async () => {
const result = await confirm();
const result = await openConfirmationModal();
setConfirmationResult(result);
}}
/>
{confirmationResult !== null ? (
<p>Confirmation result: {confirmationResult.toString()}</p>
) : null}
<ConfirmationModal />
<ConfirmationModal
{...confirmationModalProps}
title="The confirmation title"
message="A sample confirmation message"
/>
</div>
);
};
Expand Down
Loading

0 comments on commit 115e00e

Please sign in to comment.