11import {
2- createContext , useContext , useState , useMemo ,
2+ createContext , useContext , useState , useMemo , useCallback , useEffect , useRef ,
33} from 'react' ;
44import { logError } from '@edx/frontend-platform/logging' ;
55import { useIntl } from '@edx/frontend-platform/i18n' ;
66import { Toast } from '@openedx/paragon' ;
77import messages from '@src/authz-module/messages' ;
88import { DEFAULT_TOAST_DELAY , RETRY_TOAST_DELAY } from '@src/authz-module/constants' ;
9+ import { getHttpErrorStatus } from '@src/data/utils' ;
910
1011type ToastType = 'success' | 'error' | 'error-retry' ;
1112
@@ -33,7 +34,7 @@ const Br = () => <br />;
3334
3435type ToastManagerContextType = {
3536 showToast : ( toast : Omit < AppToast , 'id' > ) => void ;
36- showErrorToast : ( error , retryFn ?: ( ) => void ) => void ;
37+ showErrorToast : ( error : unknown , retryFn ?: ( ) => void ) => void ;
3738 Bold : ( chunks : React . ReactNode [ ] ) => JSX . Element ;
3839 Br : ( ) => JSX . Element ;
3940} ;
@@ -47,26 +48,33 @@ interface ToastManagerProviderProps {
4748export const ToastManagerProvider = ( { children } : ToastManagerProviderProps ) => {
4849 const intl = useIntl ( ) ;
4950 const [ toasts , setToasts ] = useState < ( AppToast & { visible : boolean } ) [ ] > ( [ ] ) ;
51+ const removalTimers = useRef < ReturnType < typeof setTimeout > [ ] > ( [ ] ) ;
5052
51- const showToast = ( toast : Omit < AppToast , 'id' > ) => {
53+ const showToast = useCallback ( ( toast : Omit < AppToast , 'id' > ) => {
5254 const id = `toast-notification-${ Date . now ( ) } -${ Math . floor ( Math . random ( ) * 1000000 ) } ` ;
5355 const newToast = { ...toast , id, visible : true } ;
5456 setToasts ( prev => [ ...prev , newToast ] ) ;
55- } ;
57+ } , [ ] ) ;
5658
57- const discardToast = ( id : string ) => {
59+ const discardToast = useCallback ( ( id : string ) => {
5860 setToasts ( prev => prev . map ( t => ( t . id === id ? { ...t , visible : false } : t ) ) ) ;
5961
60- setTimeout ( ( ) => {
62+ const timer = setTimeout ( ( ) => {
6163 setToasts ( prev => prev . filter ( t => t . id !== id ) ) ;
62- } , 5000 ) ;
63- } ;
64+ } , DEFAULT_TOAST_DELAY ) ;
65+ removalTimers . current . push ( timer ) ;
66+ } , [ ] ) ;
67+
68+ // Clear any pending removal timers when the provider unmounts.
69+ useEffect ( ( ) => ( ) => {
70+ removalTimers . current . forEach ( clearTimeout ) ;
71+ } , [ ] ) ;
6472
6573 const value = useMemo < ToastManagerContextType > ( ( ) => {
66- const showErrorToast = ( error , retryFn ?: ( ) => void ) => {
67- logError ( error ) ;
68- const errorStatus = error ?. customAttributes ?. httpErrorStatus ;
69- const toastConfig = ERROR_TOAST_MAP [ errorStatus ] || ERROR_TOAST_MAP . DEFAULT ;
74+ const showErrorToast = ( error : unknown , retryFn ?: ( ) => void ) => {
75+ logError ( error as Error ) ;
76+ const errorStatus = getHttpErrorStatus ( error ) ;
77+ const toastConfig = ( errorStatus !== undefined && ERROR_TOAST_MAP [ errorStatus ] ) || ERROR_TOAST_MAP . DEFAULT ;
7078 const message = intl . formatMessage ( messages [ toastConfig . messageId ] , { Bold, Br } ) ;
7179 /**
7280 * For retryable errors, we set a longer delay to give users more time to read the message
@@ -90,7 +98,7 @@ export const ToastManagerProvider = ({ children }: ToastManagerProviderProps) =>
9098 Bold,
9199 Br,
92100 } ) ;
93- } , [ intl ] ) ;
101+ } , [ intl , showToast ] ) ;
94102
95103 return (
96104 < ToastManagerContext . Provider value = { value } >
0 commit comments