Skip to content

Commit

Permalink
[@mantine/modals] Fix multiple closeModal issues (#3498)
Browse files Browse the repository at this point in the history
* [@mantine/modals] Fix various issues regarding usage assumptions

* [@mantine/modals] Make sure methods in ModalsContext always use the current state (#3300) (#3498)

* [@mantine/modals] Cache static methods in ModalsContext to reduce rerenderings
  • Loading branch information
cyantree committed Feb 15, 2023
1 parent 8009979 commit 7ccd515
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 72 deletions.
156 changes: 87 additions & 69 deletions src/mantine-modals/src/ModalsProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useReducer } from 'react';
import React, { useCallback, useReducer, useRef } from 'react';
import { Modal, getDefaultZIndex } from '@mantine/core';
import { randomId } from '@mantine/hooks';
import {
Expand Down Expand Up @@ -69,71 +69,88 @@ function separateConfirmModalProps(props: OpenConfirmModal) {

export function ModalsProvider({ children, modalProps, labels, modals }: ModalsProviderProps) {
const [state, dispatch] = useReducer(modalsReducer, { modals: [], current: null });
const closeAll = (canceled?: boolean) => {
state.modals.forEach((modal) => {
if (modal.type === 'confirm' && canceled) {
modal.props?.onCancel?.();
}

modal.props?.onClose?.();
});
dispatch({ type: 'CLOSE_ALL' });
};
const stateRef = useRef(state);
stateRef.current = state;

const closeAll = useCallback(
(canceled?: boolean) => {
stateRef.current.modals.forEach((modal) => {
if (modal.type === 'confirm' && canceled) {
modal.props.onCancel?.();
}

modal.props.onClose?.();
});
dispatch({ type: 'CLOSE_ALL' });
},
[stateRef, dispatch]
);

const openModal = ({ modalId, ...props }: ModalSettings) => {
const id = modalId || randomId();

dispatch({
type: 'OPEN',
payload: {
id,
type: 'content',
props,
},
});
return id;
};
const openModal = useCallback(
({ modalId, ...props }: ModalSettings) => {
const id = modalId || randomId();

dispatch({
type: 'OPEN',
payload: {
id,
type: 'content',
props,
},
});
return id;
},
[dispatch]
);

const openConfirmModal = ({ modalId, ...props }: OpenConfirmModal) => {
const id = modalId || randomId();
dispatch({
type: 'OPEN',
payload: {
id,
type: 'confirm',
props,
},
});
return id;
};
const openConfirmModal = useCallback(
({ modalId, ...props }: OpenConfirmModal) => {
const id = modalId || randomId();
dispatch({
type: 'OPEN',
payload: {
id,
type: 'confirm',
props,
},
});
return id;
},
[dispatch]
);

const openContextModal = (modal: string, { modalId, ...props }: OpenContextModal) => {
const id = modalId || randomId();
dispatch({
type: 'OPEN',
payload: {
id,
type: 'context',
props,
ctx: modal,
},
});
return id;
};
const openContextModal = useCallback(
(modal: string, { modalId, ...props }: OpenContextModal) => {
const id = modalId || randomId();
dispatch({
type: 'OPEN',
payload: {
id,
type: 'context',
props,
ctx: modal,
},
});
return id;
},
[dispatch]
);

const closeModal = (id: string, canceled?: boolean) => {
if (state.modals.length <= 1) {
closeAll(canceled);
return;
}
const closeModal = useCallback(
(id: string, canceled?: boolean) => {
const modal = stateRef.current.modals.find((item) => item.id === id);
if (!modal) {
return;
}

const modal = state.modals.find((item) => item.id === id);
if (modal?.type === 'confirm' && canceled) {
modal.props?.onCancel?.();
}
modal?.props?.onClose?.();
dispatch({ type: 'CLOSE', payload: modal.id });
};
if (modal.type === 'confirm' && canceled) {
modal.props.onCancel?.();
}
modal.props.onClose?.();
dispatch({ type: 'CLOSE', payload: modal.id });
},
[stateRef, dispatch]
);

useModalsEvents({
openModal,
Expand All @@ -153,33 +170,34 @@ export function ModalsProvider({ children, modalProps, labels, modals }: ModalsP
};

const getCurrentModal = () => {
switch (state.current?.type) {
const currentModal = stateRef.current.current;
switch (currentModal?.type) {
case 'context': {
const { innerProps, ...rest } = state.current.props;
const ContextModal = modals[state.current.ctx];
const { innerProps, ...rest } = currentModal.props;
const ContextModal = modals[currentModal.ctx];

return {
modalProps: rest,
content: <ContextModal innerProps={innerProps} context={ctx} id={state.current.id} />,
content: <ContextModal innerProps={innerProps} context={ctx} id={currentModal.id} />,
};
}
case 'confirm': {
const { modalProps: separatedModalProps, confirmProps: separatedConfirmProps } =
separateConfirmModalProps(state.current.props);
separateConfirmModalProps(currentModal.props);

return {
modalProps: separatedModalProps,
content: (
<ConfirmModal
{...separatedConfirmProps}
id={state.current.id}
labels={state.current.props.labels || labels}
id={currentModal.id}
labels={currentModal.props.labels || labels}
/>
),
};
}
case 'content': {
const { children: currentModalChildren, ...rest } = state.current.props;
const { children: currentModalChildren, ...rest } = currentModal.props;

return {
modalProps: rest,
Expand Down
7 changes: 4 additions & 3 deletions src/mantine-modals/src/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ export function modalsReducer(
};
}
case 'CLOSE': {
const modals = state.modals.filter((m) => m.id !== action.payload);
return {
current: state.modals[state.modals.length - 2] || null,
modals: state.modals.filter((m) => m.id !== action.payload),
current: modals[modals.length - 1] || null,
modals,
};
}
case 'CLOSE_ALL': {
return {
current: state.current,
current: null,
modals: [],
};
}
Expand Down

0 comments on commit 7ccd515

Please sign in to comment.