1- import React , { useContext , useEffect , useRef , useState } from 'react' ;
1+ import React , {
2+ useCallback ,
3+ useContext ,
4+ useEffect ,
5+ useRef ,
6+ useState ,
7+ } from 'react' ;
28import { Variant as ConfirmationModalVariant } from '@leafygreen-ui/confirmation-modal' ;
39import ConfirmationModal from '../components/modals/confirmation-modal' ;
410import { css } from '@leafygreen-ui/emotion' ;
@@ -22,125 +28,66 @@ type ConfirmationProperties = Partial<
2228
2329type ConfirmationCallback = ( value : boolean ) => void ;
2430
25- interface ConfirmationModalContextData {
26- showConfirmation : ( props : ConfirmationProperties ) => Promise < boolean > ;
27- isMounted : boolean ;
28- }
29-
30- type ShowConfirmationEventDetail = {
31- props : ConfirmationProperties & { confirmationId : number } ;
31+ type OnShowConfirmationProperties = {
32+ props : ConfirmationProperties ;
3233 resolve : ConfirmationCallback ;
3334 reject : ( err ?: any ) => void ;
35+ confirmationId : number ;
3436} ;
3537
36- interface ConfirmationEventMap {
37- 'show-confirmation' : CustomEvent < ShowConfirmationEventDetail > ;
38- }
39-
40- interface GlobalConfirmation extends EventTarget {
41- addEventListener < K extends keyof ConfirmationEventMap > (
42- type : K ,
43- listener : ( this : GlobalConfirmation , ev : ConfirmationEventMap [ K ] ) => void
44- ) : void ;
45- addEventListener (
46- type : string ,
47- listener : EventListenerOrEventListenerObject
48- ) : void ;
49- removeEventListener < K extends keyof ConfirmationEventMap > (
50- type : K ,
51- listener : ( this : GlobalConfirmation , ev : ConfirmationEventMap [ K ] ) => void
52- ) : void ;
53- removeEventListener (
54- type : string ,
55- listener : EventListenerOrEventListenerObject
56- ) : void ;
38+ interface ConfirmationModalActions {
39+ showConfirmation : ( props : ConfirmationProperties ) => Promise < boolean > ;
5740}
58-
59- let confirmationId = 0 ;
60-
61- class GlobalConfirmation extends EventTarget {
41+ class GlobalConfirmationModalState implements ConfirmationModalActions {
42+ private confirmationId = 0 ;
43+ onShowCallback : ( ( props : OnShowConfirmationProperties ) => void ) | null = null ;
6244 showConfirmation ( props : ConfirmationProperties ) {
6345 return new Promise < boolean > ( ( resolve , reject ) => {
64- this . dispatchEvent (
65- new CustomEvent < ShowConfirmationEventDetail > ( 'show-confirmation' , {
66- detail : {
67- props : { ...props , confirmationId : ++ confirmationId } ,
68- resolve,
69- reject,
70- } ,
71- } )
72- ) ;
46+ this . onShowCallback ?.( {
47+ props,
48+ resolve,
49+ reject,
50+ confirmationId : ++ this . confirmationId ,
51+ } ) ;
7352 } ) ;
7453 }
7554}
76- const globalConfirmation = new GlobalConfirmation ( ) ;
7755
78- export const showConfirmation =
79- globalConfirmation . showConfirmation . bind ( globalConfirmation ) ;
56+ const confirmationModalState = new GlobalConfirmationModalState ( ) ;
8057
81- const ConfirmationModalContext =
82- React . createContext < ConfirmationModalContextData > ( {
83- isMounted : false ,
84- showConfirmation,
85- } ) ;
58+ export const showConfirmation = confirmationModalState . showConfirmation . bind (
59+ confirmationModalState
60+ ) ;
8661
87- type ConfirmationModalAreaProps = Partial <
88- ShowConfirmationEventDetail [ 'props' ]
89- > & { open : boolean } ;
62+ export const showConfirmationModal = showConfirmation ;
9063
9164const hideButtonStyles = css ( {
9265 display : 'none !important' ,
9366} ) ;
9467
95- export const ConfirmationModalArea : React . FC = ( { children } ) => {
96- const hasParentContext = useContext ( ConfirmationModalContext ) . isMounted ;
97-
98- const [ confirmationProps , setConfirmationProps ] =
99- useState < ConfirmationModalAreaProps > ( {
100- open : false ,
101- confirmationId : - 1 ,
102- } ) ;
68+ const _ConfirmationModalArea : React . FunctionComponent = ( { children } ) => {
69+ const [ confirmationProps , setConfirmationProps ] = useState <
70+ Partial < ConfirmationProperties > & { open : boolean ; confirmationId : number }
71+ > ( {
72+ open : false ,
73+ confirmationId : - 1 ,
74+ } ) ;
10375 const callbackRef = useRef < ConfirmationCallback > ( ) ;
104-
105- const listenerRef =
106- useRef < ( event : CustomEvent < ShowConfirmationEventDetail > ) => void > ( ) ;
107-
108- const contextValue = React . useMemo (
109- ( ) => ( {
110- showConfirmation : ( props : ConfirmationProperties ) => {
111- return new Promise < boolean > ( ( resolve , reject ) => {
112- const event = new CustomEvent < ShowConfirmationEventDetail > (
113- 'show-confirmation' ,
114- {
115- detail : {
116- props : { ...props , confirmationId : ++ confirmationId } ,
117- resolve,
118- reject,
119- } ,
120- }
121- ) ;
122- listenerRef . current ?.( event ) ;
123- } ) ;
124- } ,
125- isMounted : true ,
126- } ) ,
127- [ ]
128- ) ;
129-
130- useEffect ( ( ) => {
131- return ( ) => {
132- callbackRef . current ?.( false ) ;
133- } ;
134- } , [ ] ) ;
135-
136- // Event listener to use confirmation modal outside of react
137- useEffect ( ( ) => {
138- const listener = ( {
139- detail : { resolve, reject, props } ,
140- } : CustomEvent < ShowConfirmationEventDetail > ) => {
141- setConfirmationProps ( { open : true , ...props } ) ;
76+ const confirmationModalStateRef = useRef < GlobalConfirmationModalState > ( ) ;
77+
78+ if ( ! confirmationModalStateRef . current ) {
79+ confirmationModalStateRef . current = confirmationModalState ;
80+ confirmationModalStateRef . current . onShowCallback = ( {
81+ props,
82+ resolve,
83+ reject,
84+ confirmationId,
85+ } ) => {
86+ setConfirmationProps ( { open : true , confirmationId, ...props } ) ;
14287 const onAbort = ( ) => {
143- setConfirmationProps ( { open : false , ...props } ) ;
88+ setConfirmationProps ( ( state ) => {
89+ return { ...state , open : false } ;
90+ } ) ;
14491 reject ( props . signal ?. reason ) ;
14592 } ;
14693 callbackRef . current = ( confirmed ) => {
@@ -149,40 +96,42 @@ export const ConfirmationModalArea: React.FC = ({ children }) => {
14996 } ;
15097 props . signal ?. addEventListener ( 'abort' , onAbort ) ;
15198 } ;
152- listenerRef . current = listener ;
153- globalConfirmation . addEventListener ( 'show-confirmation' , listener ) ;
99+ }
100+
101+ useEffect ( ( ) => {
154102 return ( ) => {
155- globalConfirmation . removeEventListener ( 'show-confirmation' , listener ) ;
103+ callbackRef . current ?.( false ) ;
104+ if ( confirmationModalStateRef . current ) {
105+ confirmationModalStateRef . current . onShowCallback = null ;
106+ }
156107 } ;
157108 } , [ ] ) ;
158109
159- const handleConfirm = ( ) => {
160- onUserAction ( true ) ;
161- } ;
162-
163- const handleCancel = ( ) => {
164- onUserAction ( false ) ;
165- } ;
166-
167- const onUserAction = ( value : boolean ) => {
168- setConfirmationProps ( ( state ) => ( { ...state , open : false } ) ) ;
110+ const onUserAction = useCallback ( ( value : boolean ) => {
111+ setConfirmationProps ( ( state ) => {
112+ return { ...state , open : false } ;
113+ } ) ;
169114 callbackRef . current ?.( value ) ;
170115 callbackRef . current = undefined ;
171- } ;
116+ } , [ ] ) ;
172117
173- if ( hasParentContext ) {
174- return < > { children } </ > ;
175- }
118+ const handleConfirm = useCallback ( ( ) => {
119+ onUserAction ( true ) ;
120+ } , [ onUserAction ] ) ;
121+
122+ const handleCancel = useCallback ( ( ) => {
123+ onUserAction ( false ) ;
124+ } , [ onUserAction ] ) ;
176125
177126 return (
178- < ConfirmationModalContext . Provider value = { contextValue } >
127+ < >
179128 { children }
180129 < ConfirmationModal
181130 // To make sure that confirmation modal internal state is reset for
182131 // every confirmation request triggered with showConfirmation method we
183132 // pass `confirmationId` as a component key to force React to remount it
184133 // when request starts
185- key = { confirmationId }
134+ key = { confirmationProps . confirmationId }
186135 data-testid = { confirmationProps [ 'data-testid' ] ?? 'confirmation-modal' }
187136 open = { confirmationProps . open }
188137 title = { confirmationProps . title ?? 'Are you sure?' }
@@ -205,16 +154,22 @@ export const ConfirmationModalArea: React.FC = ({ children }) => {
205154 >
206155 { confirmationProps . description }
207156 </ ConfirmationModal >
208- </ ConfirmationModalContext . Provider >
157+ </ >
209158 ) ;
210159} ;
211160
212- export const useConfirmationModal = ( ) => {
213- const { isMounted, showConfirmation } = useContext ( ConfirmationModalContext ) ;
214- if ( ! isMounted ) {
215- throw new Error (
216- 'useConfirmationModal must be used within a ConfirmationModalArea'
217- ) ;
161+ const ConfirmationModalAreaMountedContext = React . createContext ( false ) ;
162+
163+ export const ConfirmationModalArea : React . FunctionComponent = ( {
164+ children,
165+ } ) => {
166+ if ( useContext ( ConfirmationModalAreaMountedContext ) ) {
167+ return < > { children } </ > ;
218168 }
219- return { showConfirmation } ;
169+
170+ return (
171+ < ConfirmationModalAreaMountedContext . Provider value = { true } >
172+ < _ConfirmationModalArea > { children } </ _ConfirmationModalArea >
173+ </ ConfirmationModalAreaMountedContext . Provider >
174+ ) ;
220175} ;
0 commit comments