From fe0d0a2ec7be76c4d9d1bc2954ac59de619f169b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 5 Dec 2022 14:22:44 +0100 Subject: [PATCH] core-components: handle overlapping display of transient alert messages Signed-off-by: Patrik Oldsberg --- .../AlertDisplay/AlertDisplay.test.tsx | 33 +++++++++++++++++++ .../components/AlertDisplay/AlertDisplay.tsx | 11 ++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx b/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx index bff018cc49..d21adf1479 100644 --- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx +++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.test.tsx @@ -214,6 +214,39 @@ describe('', () => { expect(queryByText('transient message')).not.toBeInTheDocument(); jest.useRealTimers(); }); + + it('renders 3 different messages with overlapping timeout', 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(); + + // 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(); + + // 3.5s in, message 3 now shown + act(() => jest.advanceTimersByTime(1500)); + expect(queryByText('transient message three')).toBeInTheDocument(); + + // 5s 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 e85887c174..fe20b1d66d 100644 --- a/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx +++ b/packages/core-components/src/components/AlertDisplay/AlertDisplay.tsx @@ -19,6 +19,7 @@ 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'; @@ -62,6 +63,7 @@ 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' }, @@ -82,13 +84,14 @@ export function AlertDisplay(props: AlertDisplayProps) { useEffect(() => { const [current] = messages; if (current && current.display === 'transient') { - const timer = setTimeout(() => { - setMessages(msgs => msgs.filter(msg => msg !== current)); + setTimeout(() => { + if (isMounted()) { + setMessages(msgs => msgs.filter(msg => msg !== current)); + } }, timeoutMs); - return () => clearTimeout(timer); } return undefined; - }, [messages, timeoutMs]); + }, [messages, timeoutMs, isMounted]); if (messages.length === 0) { return null;