-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[Payment due @abzokhattab] Refactor ReanimatedModal/index.tsx to follow Rules of React and compile with React Compiler #90358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cf4a7e7
ef8e8a4
3ee0a21
3326f87
173165e
d3bb13f
d2809b9
926a2f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import noop from 'lodash/noop'; | ||
| import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; | ||
| import React, {useEffect, useEffectEvent, useRef, useState} from 'react'; | ||
| import type {NativeEventSubscription, ViewStyle} from 'react-native'; | ||
| // eslint-disable-next-line no-restricted-imports | ||
| import {BackHandler, InteractionManager, Modal, StyleSheet, View} from 'react-native'; | ||
|
|
@@ -55,37 +55,64 @@ function ReanimatedModal({ | |
| shouldReturnFocus, | ||
| ...props | ||
| }: ReanimatedModalProps) { | ||
| const [isVisibleState, setIsVisibleState] = useState(isVisible); | ||
| const [isContainerOpen, setIsContainerOpen] = useState(false); | ||
| const [isTransitioning, setIsTransitioning] = useState(false); | ||
| const {windowWidth, windowHeight} = useWindowDimensions(); | ||
| const styles = useThemeStyles(); | ||
|
|
||
| const backHandlerListener = useRef<NativeEventSubscription | null>(null); | ||
| const handleRef = useRef<number | undefined>(undefined); | ||
| const transitionHandleRef = useRef<TransitionHandle | null>(null); | ||
|
|
||
| const styles = useThemeStyles(); | ||
| const isTransitioning = isVisible !== isContainerOpen; | ||
| const backdropStyle: ViewStyle = {width: windowWidth, height: windowHeight, backgroundColor: backdropColor}; | ||
| const modalStyle = {zIndex: StyleSheet.flatten(style)?.zIndex}; | ||
|
|
||
| const onBackButtonPressHandler = useCallback(() => { | ||
| const onBackButtonPressHandler = () => { | ||
| if (shouldIgnoreBackHandlerDuringTransition && isTransitioning) { | ||
| return false; | ||
| } | ||
| if (isVisibleState) { | ||
| if (isVisible) { | ||
| onBackButtonPress(); | ||
| return true; | ||
| } | ||
| return false; | ||
| }, [isVisibleState, onBackButtonPress, isTransitioning, shouldIgnoreBackHandlerDuringTransition]); | ||
| }; | ||
|
|
||
| const handleEscape = useCallback( | ||
| (e: KeyboardEvent) => { | ||
| if (e.key !== 'Escape' || onBackButtonPressHandler() !== true) { | ||
| return; | ||
| } | ||
| e.stopImmediatePropagation(); | ||
| }, | ||
| [onBackButtonPressHandler], | ||
| ); | ||
| const handleEscape = (e: KeyboardEvent) => { | ||
| if (e.key !== 'Escape' || onBackButtonPressHandler() !== true) { | ||
| return; | ||
| } | ||
| e.stopImmediatePropagation(); | ||
| }; | ||
|
|
||
| const clearTransitionHandles = () => { | ||
| if (handleRef.current) { | ||
| InteractionManager.clearInteractionHandle(handleRef.current); | ||
| handleRef.current = undefined; | ||
| } | ||
| if (transitionHandleRef.current) { | ||
| TransitionTracker.endTransition(transitionHandleRef.current); | ||
| transitionHandleRef.current = null; | ||
| } | ||
| }; | ||
|
|
||
| const onOpenCallBack = () => { | ||
| setIsContainerOpen(true); | ||
| clearTransitionHandles(); | ||
| onModalShow(); | ||
| }; | ||
|
|
||
| const onCloseCallBack = () => { | ||
| setIsContainerOpen(false); | ||
| clearTransitionHandles(); | ||
|
|
||
| // Because on Android, the Modal's onDismiss callback does not work reliably. There's a reported issue at: | ||
| // https://stackoverflow.com/questions/58937956/react-native-modal-ondismiss-not-invoked | ||
| // Therefore, we manually call onModalHide() here for Android. | ||
| if (getPlatform() === CONST.PLATFORM.ANDROID) { | ||
| onModalHide(); | ||
| } | ||
| }; | ||
|
|
||
| useEffect(() => { | ||
| if (getPlatform() === CONST.PLATFORM.WEB) { | ||
|
|
@@ -103,82 +130,29 @@ function ReanimatedModal({ | |
| }; | ||
| }, [handleEscape, onBackButtonPressHandler]); | ||
|
|
||
| useEffect( | ||
| () => () => { | ||
| if (handleRef.current) { | ||
| InteractionManager.clearInteractionHandle(handleRef.current); | ||
| } | ||
| if (transitionHandleRef.current) { | ||
| TransitionTracker.endTransition(transitionHandleRef.current); | ||
| transitionHandleRef.current = null; | ||
| } | ||
|
|
||
| setIsVisibleState(false); | ||
| setIsContainerOpen(false); | ||
| }, | ||
|
|
||
| [], | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| if (isVisible && !isContainerOpen && !isTransitioning) { | ||
| handleRef.current = InteractionManager.createInteractionHandle(); | ||
| transitionHandleRef.current = TransitionTracker.startTransition(); | ||
| onModalWillShow(); | ||
|
|
||
| setIsVisibleState(true); | ||
| setIsTransitioning(true); | ||
| } else if (!isVisible && isContainerOpen && !isTransitioning) { | ||
| if (isTransitioning) { | ||
| handleRef.current = InteractionManager.createInteractionHandle(); | ||
| transitionHandleRef.current = TransitionTracker.startTransition(); | ||
| onModalWillHide(); | ||
|
|
||
| blurActiveElement(); | ||
| setIsVisibleState(false); | ||
| setIsTransitioning(true); | ||
| } | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, [isVisible, isContainerOpen, isTransitioning]); | ||
|
|
||
| const backdropStyle: ViewStyle = useMemo(() => { | ||
| return {width: windowWidth, height: windowHeight, backgroundColor: backdropColor}; | ||
| }, [windowWidth, windowHeight, backdropColor]); | ||
|
|
||
| const onOpenCallBack = useCallback(() => { | ||
| setIsTransitioning(false); | ||
| setIsContainerOpen(true); | ||
| if (handleRef.current) { | ||
| InteractionManager.clearInteractionHandle(handleRef.current); | ||
| } | ||
| if (transitionHandleRef.current) { | ||
| TransitionTracker.endTransition(transitionHandleRef.current); | ||
| transitionHandleRef.current = null; | ||
| } | ||
| onModalShow(); | ||
| }, [onModalShow]); | ||
|
|
||
| const onCloseCallBack = useCallback(() => { | ||
| setIsTransitioning(false); | ||
| setIsContainerOpen(false); | ||
| if (handleRef.current) { | ||
| InteractionManager.clearInteractionHandle(handleRef.current); | ||
| } | ||
| if (transitionHandleRef.current) { | ||
| TransitionTracker.endTransition(transitionHandleRef.current); | ||
| transitionHandleRef.current = null; | ||
| } | ||
| return () => { | ||
| clearTransitionHandles(); | ||
| }; | ||
| }, [isTransitioning]); | ||
|
|
||
| // Because on Android, the Modal's onDismiss callback does not work reliably. There's a reported issue at: | ||
| // https://stackoverflow.com/questions/58937956/react-native-modal-ondismiss-not-invoked | ||
| // Therefore, we manually call onModalHide() here for Android. | ||
| if (getPlatform() === CONST.PLATFORM.ANDROID) { | ||
| onModalHide(); | ||
| const fireTransitionCallbacks = useEffectEvent(() => { | ||
| if (isVisible && !isContainerOpen) { | ||
| onModalWillShow(); | ||
| } else if (!isVisible && isContainerOpen) { | ||
| onModalWillHide(); | ||
| blurActiveElement(); | ||
| } | ||
| }, [onModalHide]); | ||
| }); | ||
|
|
||
| const modalStyle = useMemo(() => { | ||
| return {zIndex: StyleSheet.flatten(style)?.zIndex}; | ||
| }, [style]); | ||
| useEffect(() => { | ||
| fireTransitionCallbacks(); | ||
| }, [isVisible, isContainerOpen]); | ||
|
|
||
| const containerView = ( | ||
| <Container | ||
|
|
@@ -212,7 +186,7 @@ function ReanimatedModal({ | |
| /> | ||
| ); | ||
|
|
||
| if (!coverScreen && isVisibleState) { | ||
| if (!coverScreen && isVisible) { | ||
| return ( | ||
| <View | ||
| pointerEvents="box-none" | ||
|
|
@@ -223,8 +197,8 @@ function ReanimatedModal({ | |
| </View> | ||
| ); | ||
| } | ||
| const isBackdropMounted = isVisibleState || ((isTransitioning || isContainerOpen !== isVisibleState) && getPlatform() === CONST.PLATFORM.WEB); | ||
| const modalVisibility = isVisibleState || isTransitioning || isContainerOpen !== isVisibleState; | ||
| const isBackdropMounted = isVisible || (isTransitioning && getPlatform() === CONST.PLATFORM.WEB); | ||
| const modalVisibility = isVisible || isTransitioning; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If a modal is dismissed while its enter animation is still running, Useful? React with 👍 / 👎. |
||
| return ( | ||
| <LayoutAnimationConfig skipExiting={getPlatform() !== CONST.PLATFORM.WEB}> | ||
| <Modal | ||
|
|
@@ -251,7 +225,7 @@ function ReanimatedModal({ | |
| pointerEvents="box-none" | ||
| style={[style, {margin: 0}]} | ||
| > | ||
| {isVisibleState && containerView} | ||
| {isVisible && containerView} | ||
| </KeyboardAvoidingView> | ||
| ) : ( | ||
| <FocusTrapForModal | ||
|
|
@@ -260,7 +234,7 @@ function ReanimatedModal({ | |
| shouldReturnFocus={shouldReturnFocus ?? !shouldEnableNewFocusManagement} | ||
| shouldPreventScroll={shouldPreventScrollOnFocus} | ||
| > | ||
| {isVisibleState && containerView} | ||
| {isVisible && containerView} | ||
| </FocusTrapForModal> | ||
| )} | ||
| </Modal> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.