11import noop from 'lodash/noop' ;
2- import React , { useEffect , useEffectEvent , useRef , useState } from 'react' ;
2+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
33import type { NativeEventSubscription , ViewStyle } from 'react-native' ;
44// eslint-disable-next-line no-restricted-imports
55import { BackHandler , InteractionManager , Modal , StyleSheet , View } from 'react-native' ;
@@ -55,64 +55,37 @@ function ReanimatedModal({
5555 shouldReturnFocus,
5656 ...props
5757} : ReanimatedModalProps ) {
58+ const [ isVisibleState , setIsVisibleState ] = useState ( isVisible ) ;
5859 const [ isContainerOpen , setIsContainerOpen ] = useState ( false ) ;
60+ const [ isTransitioning , setIsTransitioning ] = useState ( false ) ;
5961 const { windowWidth, windowHeight} = useWindowDimensions ( ) ;
60- const styles = useThemeStyles ( ) ;
6162
6263 const backHandlerListener = useRef < NativeEventSubscription | null > ( null ) ;
6364 const handleRef = useRef < number | undefined > ( undefined ) ;
6465 const transitionHandleRef = useRef < TransitionHandle | null > ( null ) ;
6566
66- const isTransitioning = isVisible !== isContainerOpen ;
67- const backdropStyle : ViewStyle = { width : windowWidth , height : windowHeight , backgroundColor : backdropColor } ;
68- const modalStyle = { zIndex : StyleSheet . flatten ( style ) ?. zIndex } ;
67+ const styles = useThemeStyles ( ) ;
6968
70- const onBackButtonPressHandler = ( ) => {
69+ const onBackButtonPressHandler = useCallback ( ( ) => {
7170 if ( shouldIgnoreBackHandlerDuringTransition && isTransitioning ) {
7271 return false ;
7372 }
74- if ( isVisible ) {
73+ if ( isVisibleState ) {
7574 onBackButtonPress ( ) ;
7675 return true ;
7776 }
7877 return false ;
79- } ;
80-
81- const handleEscape = ( e : KeyboardEvent ) => {
82- if ( e . key !== 'Escape' || onBackButtonPressHandler ( ) !== true ) {
83- return ;
84- }
85- e . stopImmediatePropagation ( ) ;
86- } ;
87-
88- const clearTransitionHandles = ( ) => {
89- if ( handleRef . current ) {
90- InteractionManager . clearInteractionHandle ( handleRef . current ) ;
91- handleRef . current = undefined ;
92- }
93- if ( transitionHandleRef . current ) {
94- TransitionTracker . endTransition ( transitionHandleRef . current ) ;
95- transitionHandleRef . current = null ;
96- }
97- } ;
98-
99- const onOpenCallBack = ( ) => {
100- setIsContainerOpen ( true ) ;
101- clearTransitionHandles ( ) ;
102- onModalShow ( ) ;
103- } ;
104-
105- const onCloseCallBack = ( ) => {
106- setIsContainerOpen ( false ) ;
107- clearTransitionHandles ( ) ;
78+ } , [ isVisibleState , onBackButtonPress , isTransitioning , shouldIgnoreBackHandlerDuringTransition ] ) ;
10879
109- // Because on Android, the Modal's onDismiss callback does not work reliably. There's a reported issue at:
110- // https://stackoverflow.com/questions/58937956/react-native-modal-ondismiss-not-invoked
111- // Therefore, we manually call onModalHide() here for Android.
112- if ( getPlatform ( ) === CONST . PLATFORM . ANDROID ) {
113- onModalHide ( ) ;
114- }
115- } ;
80+ const handleEscape = useCallback (
81+ ( e : KeyboardEvent ) => {
82+ if ( e . key !== 'Escape' || onBackButtonPressHandler ( ) !== true ) {
83+ return ;
84+ }
85+ e . stopImmediatePropagation ( ) ;
86+ } ,
87+ [ onBackButtonPressHandler ] ,
88+ ) ;
11689
11790 useEffect ( ( ) => {
11891 if ( getPlatform ( ) === CONST . PLATFORM . WEB ) {
@@ -130,29 +103,86 @@ function ReanimatedModal({
130103 } ;
131104 } , [ handleEscape , onBackButtonPressHandler ] ) ;
132105
106+ useEffect (
107+ ( ) => ( ) => {
108+ if ( handleRef . current ) {
109+ // eslint-disable-next-line @typescript-eslint/no-deprecated
110+ InteractionManager . clearInteractionHandle ( handleRef . current ) ;
111+ }
112+ if ( transitionHandleRef . current ) {
113+ TransitionTracker . endTransition ( transitionHandleRef . current ) ;
114+ transitionHandleRef . current = null ;
115+ }
116+
117+ setIsVisibleState ( false ) ;
118+ setIsContainerOpen ( false ) ;
119+ } ,
120+
121+ [ ] ,
122+ ) ;
123+
133124 useEffect ( ( ) => {
134- if ( isTransitioning ) {
125+ if ( isVisible && ! isContainerOpen && ! isTransitioning ) {
126+ // eslint-disable-next-line @typescript-eslint/no-deprecated
135127 handleRef . current = InteractionManager . createInteractionHandle ( ) ;
136128 transitionHandleRef . current = TransitionTracker . startTransition ( ) ;
137- }
138-
139- return ( ) => {
140- clearTransitionHandles ( ) ;
141- } ;
142- } , [ isTransitioning ] ) ;
143-
144- const fireTransitionCallbacks = useEffectEvent ( ( ) => {
145- if ( isVisible && ! isContainerOpen ) {
146129 onModalWillShow ( ) ;
147- } else if ( ! isVisible && isContainerOpen ) {
130+
131+ // eslint-disable-next-line react-hooks/set-state-in-effect
132+ setIsVisibleState ( true ) ;
133+ setIsTransitioning ( true ) ;
134+ } else if ( ! isVisible && isContainerOpen && ! isTransitioning ) {
135+ handleRef . current = InteractionManager . createInteractionHandle ( ) ;
136+ transitionHandleRef . current = TransitionTracker . startTransition ( ) ;
148137 onModalWillHide ( ) ;
138+
149139 blurActiveElement ( ) ;
140+ setIsVisibleState ( false ) ;
141+ setIsTransitioning ( true ) ;
150142 }
151- } ) ;
143+ // eslint-disable-next-line react-hooks/exhaustive-deps
144+ } , [ isVisible , isContainerOpen , isTransitioning ] ) ;
152145
153- useEffect ( ( ) => {
154- fireTransitionCallbacks ( ) ;
155- } , [ isVisible , isContainerOpen ] ) ;
146+ const backdropStyle : ViewStyle = useMemo ( ( ) => {
147+ return { width : windowWidth , height : windowHeight , backgroundColor : backdropColor } ;
148+ } , [ windowWidth , windowHeight , backdropColor ] ) ;
149+
150+ const onOpenCallBack = useCallback ( ( ) => {
151+ setIsTransitioning ( false ) ;
152+ setIsContainerOpen ( true ) ;
153+ if ( handleRef . current ) {
154+ // eslint-disable-next-line @typescript-eslint/no-deprecated
155+ InteractionManager . clearInteractionHandle ( handleRef . current ) ;
156+ }
157+ if ( transitionHandleRef . current ) {
158+ TransitionTracker . endTransition ( transitionHandleRef . current ) ;
159+ transitionHandleRef . current = null ;
160+ }
161+ onModalShow ( ) ;
162+ } , [ onModalShow ] ) ;
163+
164+ const onCloseCallBack = useCallback ( ( ) => {
165+ setIsTransitioning ( false ) ;
166+ setIsContainerOpen ( false ) ;
167+ if ( handleRef . current ) {
168+ InteractionManager . clearInteractionHandle ( handleRef . current ) ;
169+ }
170+ if ( transitionHandleRef . current ) {
171+ TransitionTracker . endTransition ( transitionHandleRef . current ) ;
172+ transitionHandleRef . current = null ;
173+ }
174+
175+ // Because on Android, the Modal's onDismiss callback does not work reliably. There's a reported issue at:
176+ // https://stackoverflow.com/questions/58937956/react-native-modal-ondismiss-not-invoked
177+ // Therefore, we manually call onModalHide() here for Android.
178+ if ( getPlatform ( ) === CONST . PLATFORM . ANDROID ) {
179+ onModalHide ( ) ;
180+ }
181+ } , [ onModalHide ] ) ;
182+
183+ const modalStyle = useMemo ( ( ) => {
184+ return { zIndex : StyleSheet . flatten ( style ) ?. zIndex } ;
185+ } , [ style ] ) ;
156186
157187 const containerView = (
158188 < Container
@@ -186,7 +216,7 @@ function ReanimatedModal({
186216 />
187217 ) ;
188218
189- if ( ! coverScreen && isVisible ) {
219+ if ( ! coverScreen && isVisibleState ) {
190220 return (
191221 < View
192222 pointerEvents = "box-none"
@@ -197,8 +227,8 @@ function ReanimatedModal({
197227 </ View >
198228 ) ;
199229 }
200- const isBackdropMounted = isVisible || ( isTransitioning && getPlatform ( ) === CONST . PLATFORM . WEB ) ;
201- const modalVisibility = isVisible || isTransitioning ;
230+ const isBackdropMounted = isVisibleState || ( ( isTransitioning || isContainerOpen !== isVisibleState ) && getPlatform ( ) === CONST . PLATFORM . WEB ) ;
231+ const modalVisibility = isVisibleState || isTransitioning || isContainerOpen !== isVisibleState ;
202232 return (
203233 < LayoutAnimationConfig skipExiting = { getPlatform ( ) !== CONST . PLATFORM . WEB } >
204234 < Modal
@@ -225,7 +255,7 @@ function ReanimatedModal({
225255 pointerEvents = "box-none"
226256 style = { [ style , { margin : 0 } ] }
227257 >
228- { isVisible && containerView }
258+ { isVisibleState && containerView }
229259 </ KeyboardAvoidingView >
230260 ) : (
231261 < FocusTrapForModal
@@ -234,7 +264,7 @@ function ReanimatedModal({
234264 shouldReturnFocus = { shouldReturnFocus ?? ! shouldEnableNewFocusManagement }
235265 shouldPreventScroll = { shouldPreventScrollOnFocus }
236266 >
237- { isVisible && containerView }
267+ { isVisibleState && containerView }
238268 </ FocusTrapForModal >
239269 ) }
240270 </ Modal >
0 commit comments