diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx
index d21adf1479..b75f773f90 100644
--- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx
+++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx
@@ -247,6 +247,43 @@ describe('', () => {
expect(queryByText('transient message three')).not.toBeInTheDocument();
jest.useRealTimers();
});
+
+ it('renders 3 different messages with overlapping timeout and manual removal', async () => {
+ jest.useFakeTimers();
+ const { queryByText } = await renderInTestApp(
+
+
+ ,
+ );
+
+ // 3 messages stacked with 1.5s each, display times: 0-1.5, 1.5-3, 3-4.5
+
+ // 0s in, only message 1
+ expect(queryByText('transient message one')).toBeInTheDocument();
+
+ // 1s in, message 1 still shown, message 2 added in background
+ act(() => jest.advanceTimersByTime(1000));
+ expect(queryByText('transient message one')).toBeInTheDocument();
+ expect(queryByText('(1 older message)')).toBeInTheDocument();
+
+ // manually remove message 1
+ fireEvent.click(screen.getByTestId('error-button-close'));
+ expect(screen.getByText('transient message two')).toBeInTheDocument();
+
+ // 2s in, message 2 now shown, message 3 added
+ act(() => jest.advanceTimersByTime(1000));
+ expect(queryByText('transient message two')).toBeInTheDocument();
+ expect(queryByText('(1 older message)')).toBeInTheDocument();
+
+ // 3s in, message 3 now shown
+ act(() => jest.advanceTimersByTime(1500));
+ expect(queryByText('transient message three')).toBeInTheDocument();
+
+ // 4s in, all messages gone
+ act(() => jest.advanceTimersByTime(1500));
+ expect(queryByText('transient message three')).not.toBeInTheDocument();
+ jest.useRealTimers();
+ });
});
describe('with multiple messages of mixed display', () => {
diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx
index fe20b1d66d..f1021b1f8b 100644
--- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx
+++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx
@@ -19,7 +19,6 @@ import Snackbar from '@material-ui/core/Snackbar';
import Typography from '@material-ui/core/Typography';
import CloseIcon from '@material-ui/icons/Close';
import { Alert } from '@material-ui/lab';
-import { useIsMounted } from '@react-hookz/web';
import pluralize from 'pluralize';
import React, { useEffect, useState } from 'react';
@@ -63,7 +62,6 @@ export type AlertDisplayProps = {
export function AlertDisplay(props: AlertDisplayProps) {
const [messages, setMessages] = useState>([]);
const alertApi = useApi(alertApiRef);
- const isMounted = useIsMounted();
const {
anchorOrigin = { vertical: 'top', horizontal: 'center' },
@@ -81,24 +79,25 @@ export function AlertDisplay(props: AlertDisplayProps) {
};
}, [alertApi]);
+ const [firstMessage] = messages;
+
useEffect(() => {
- const [current] = messages;
- if (current && current.display === 'transient') {
- setTimeout(() => {
- if (isMounted()) {
- setMessages(msgs => msgs.filter(msg => msg !== current));
- }
+ if (firstMessage && firstMessage.display === 'transient') {
+ const timeout = setTimeout(() => {
+ setMessages(msgs => {
+ const newMsgs = msgs.filter(msg => msg !== firstMessage);
+ return newMsgs.length === msgs.length ? msgs : newMsgs;
+ });
}, timeoutMs);
+ return () => clearTimeout(timeout);
}
return undefined;
- }, [messages, timeoutMs, isMounted]);
+ }, [firstMessage, timeoutMs]);
if (messages.length === 0) {
return null;
}
- const [firstMessage] = messages;
-
const handleClose = () => {
setMessages(msgs => msgs.filter(msg => msg !== firstMessage));
};