diff --git a/src/components/Modal/BaseModal.js b/src/components/Modal/BaseModal.js
index 79dd98d0e876..8035eca3d96a 100644
--- a/src/components/Modal/BaseModal.js
+++ b/src/components/Modal/BaseModal.js
@@ -1,15 +1,17 @@
-import React, {PureComponent} from 'react';
+import React, {forwardRef, useCallback, useEffect, useMemo} from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import ReactNativeModal from 'react-native-modal';
-import {SafeAreaInsetsContext} from 'react-native-safe-area-context';
+import {useSafeAreaInsets} from 'react-native-safe-area-context';
import styles from '../../styles/styles';
+import * as Modal from '../../libs/actions/Modal';
import * as StyleUtils from '../../styles/StyleUtils';
import themeColors from '../../styles/themes/default';
import {propTypes as modalPropTypes, defaultProps as modalDefaultProps} from './modalPropTypes';
-import * as Modal from '../../libs/actions/Modal';
import getModalStyles from '../../styles/getModalStyles';
+import useWindowDimensions from '../../hooks/useWindowDimensions';
import variables from '../../styles/variables';
+import CONST from '../../CONST';
import ComposerFocusManager from '../../libs/ComposerFocusManager';
const propTypes = {
@@ -24,173 +26,188 @@ const defaultProps = {
forwardedRef: () => {},
};
-class BaseModal extends PureComponent {
- constructor(props) {
- super(props);
-
- this.hideModal = this.hideModal.bind(this);
- }
-
- componentDidMount() {
- if (!this.props.isVisible) {
- return;
- }
-
- Modal.willAlertModalBecomeVisible(true);
-
- // To handle closing any modal already visible when this modal is mounted, i.e. PopoverReportActionContextMenu
- Modal.setCloseModal(this.props.onClose);
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.isVisible === this.props.isVisible) {
- return;
- }
-
- Modal.willAlertModalBecomeVisible(this.props.isVisible);
- Modal.setCloseModal(this.props.isVisible ? this.props.onClose : null);
- }
-
- componentWillUnmount() {
- // Only trigger onClose and setModalVisibility if the modal is unmounting while visible.
- if (this.props.isVisible) {
- this.hideModal(true);
- Modal.willAlertModalBecomeVisible(false);
- }
-
- // To prevent closing any modal already unmounted when this modal still remains as visible state
- Modal.setCloseModal(null);
- }
+function BaseModal({
+ isVisible,
+ onClose,
+ shouldSetModalVisibility,
+ onModalHide,
+ type,
+ popoverAnchorPosition,
+ innerContainerStyle,
+ outerStyle,
+ onModalShow,
+ propagateSwipe,
+ fullscreen,
+ animationIn,
+ animationOut,
+ useNativeDriver,
+ hideModalContentWhileAnimating,
+ animationInTiming,
+ animationOutTiming,
+ statusBarTranslucent,
+ onLayout,
+ avoidKeyboard,
+ forwardedRef,
+ children,
+}) {
+ const {windowWidth, windowHeight, isSmallScreenWidth} = useWindowDimensions();
+
+ const safeAreaInsets = useSafeAreaInsets();
/**
* Hides modal
* @param {Boolean} [callHideCallback=true] Should we call the onModalHide callback
*/
- hideModal(callHideCallback = true) {
- if (this.props.shouldSetModalVisibility) {
- Modal.setModalVisibility(false);
- }
- if (callHideCallback) {
- this.props.onModalHide();
+ const hideModal = useCallback(
+ (callHideCallback = true) => {
+ if (shouldSetModalVisibility) {
+ Modal.setModalVisibility(false);
+ }
+ if (callHideCallback) {
+ onModalHide();
+ }
+ Modal.onModalDidClose();
+ if (!fullscreen) {
+ ComposerFocusManager.setReadyToFocus();
+ }
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- adding onModalHide to the dependency array causes too many unnecessary rerenders
+ [shouldSetModalVisibility],
+ );
+
+ useEffect(() => {
+ Modal.willAlertModalBecomeVisible(isVisible);
+
+ // To handle closing any modal already visible when this modal is mounted, i.e. PopoverReportActionContextMenu
+ Modal.setCloseModal(isVisible ? onClose : null);
+
+ return () => {
+ // Only trigger onClose and setModalVisibility if the modal is unmounting while visible.
+ if (isVisible) {
+ hideModal(true);
+ Modal.willAlertModalBecomeVisible(false);
+ }
+
+ // To prevent closing any modal already unmounted when this modal still remains as visible state
+ Modal.setCloseModal(null);
+ };
+ }, [hideModal, isVisible, onClose]);
+
+ const handleShowModal = () => {
+ if (shouldSetModalVisibility) {
+ Modal.setModalVisibility(true);
}
- Modal.onModalDidClose();
- if (!this.props.fullscreen) {
- ComposerFocusManager.setReadyToFocus();
+ onModalShow();
+ };
+
+ const handleBackdropPress = (e) => {
+ if (e && e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) {
+ return;
}
- }
-
- render() {
- const {
- modalStyle,
- modalContainerStyle,
- swipeDirection,
- animationIn,
- animationOut,
- shouldAddTopSafeAreaMargin,
- shouldAddBottomSafeAreaMargin,
- shouldAddTopSafeAreaPadding,
- shouldAddBottomSafeAreaPadding,
- hideBackdrop,
- } = getModalStyles(
- this.props.type,
- {
- windowWidth: this.props.windowWidth,
- windowHeight: this.props.windowHeight,
- isSmallScreenWidth: this.props.isSmallScreenWidth,
- },
- this.props.popoverAnchorPosition,
- this.props.innerContainerStyle,
- this.props.outerStyle,
- );
- return (
- {
- if (e && e.key === 'Enter') {
- return;
- }
- this.props.onClose();
- }}
- // Note: Escape key on web/desktop will trigger onBackButtonPress callback
- // eslint-disable-next-line react/jsx-props-no-multi-spaces
- onBackButtonPress={this.props.onClose}
- onModalWillShow={() => {
- ComposerFocusManager.resetReadyToFocus();
- }}
- onModalShow={() => {
- if (this.props.shouldSetModalVisibility) {
- Modal.setModalVisibility(true);
- }
- this.props.onModalShow();
- }}
- propagateSwipe={this.props.propagateSwipe}
- onModalHide={this.hideModal}
- onDismiss={() => ComposerFocusManager.setReadyToFocus()}
- onSwipeComplete={this.props.onClose}
- swipeDirection={swipeDirection}
- isVisible={this.props.isVisible}
- backdropColor={themeColors.overlay}
- backdropOpacity={hideBackdrop ? 0 : variables.overlayOpacity}
- backdropTransitionOutTiming={0}
- hasBackdrop={this.props.fullscreen}
- coverScreen={this.props.fullscreen}
- style={modalStyle}
- deviceHeight={this.props.windowHeight}
- deviceWidth={this.props.windowWidth}
- animationIn={this.props.animationIn || animationIn}
- animationOut={this.props.animationOut || animationOut}
- useNativeDriver={this.props.useNativeDriver}
- hideModalContentWhileAnimating={this.props.hideModalContentWhileAnimating}
- animationInTiming={this.props.animationInTiming}
- animationOutTiming={this.props.animationOutTiming}
- statusBarTranslucent={this.props.statusBarTranslucent}
- onLayout={this.props.onLayout}
- avoidKeyboard={this.props.avoidKeyboard}
+ onClose();
+ };
+
+ const handleDismissModal = () => {
+ ComposerFocusManager.setReadyToFocus();
+ };
+
+ const {
+ modalStyle,
+ modalContainerStyle,
+ swipeDirection,
+ animationIn: modalStyleAnimationIn,
+ animationOut: modalStyleAnimationOut,
+ shouldAddTopSafeAreaMargin,
+ shouldAddBottomSafeAreaMargin,
+ shouldAddTopSafeAreaPadding,
+ shouldAddBottomSafeAreaPadding,
+ hideBackdrop,
+ } = useMemo(
+ () =>
+ getModalStyles(
+ type,
+ {
+ windowWidth,
+ windowHeight,
+ isSmallScreenWidth,
+ },
+ popoverAnchorPosition,
+ innerContainerStyle,
+ outerStyle,
+ ),
+ [innerContainerStyle, isSmallScreenWidth, outerStyle, popoverAnchorPosition, type, windowHeight, windowWidth],
+ );
+
+ const {
+ paddingTop: safeAreaPaddingTop,
+ paddingBottom: safeAreaPaddingBottom,
+ paddingLeft: safeAreaPaddingLeft,
+ paddingRight: safeAreaPaddingRight,
+ } = StyleUtils.getSafeAreaPadding(safeAreaInsets);
+
+ const modalPaddingStyles = StyleUtils.getModalPaddingStyles({
+ safeAreaPaddingTop,
+ safeAreaPaddingBottom,
+ safeAreaPaddingLeft,
+ safeAreaPaddingRight,
+ shouldAddBottomSafeAreaMargin,
+ shouldAddTopSafeAreaMargin,
+ shouldAddBottomSafeAreaPadding,
+ shouldAddTopSafeAreaPadding,
+ modalContainerStyleMarginTop: modalContainerStyle.marginTop,
+ modalContainerStyleMarginBottom: modalContainerStyle.marginBottom,
+ modalContainerStylePaddingTop: modalContainerStyle.paddingTop,
+ modalContainerStylePaddingBottom: modalContainerStyle.paddingBottom,
+ insets: safeAreaInsets,
+ });
+
+ return (
+
+
-
- {(insets) => {
- const {
- paddingTop: safeAreaPaddingTop,
- paddingBottom: safeAreaPaddingBottom,
- paddingLeft: safeAreaPaddingLeft,
- paddingRight: safeAreaPaddingRight,
- } = StyleUtils.getSafeAreaPadding(insets);
-
- const modalPaddingStyles = StyleUtils.getModalPaddingStyles({
- safeAreaPaddingTop,
- safeAreaPaddingBottom,
- safeAreaPaddingLeft,
- safeAreaPaddingRight,
- shouldAddBottomSafeAreaMargin,
- shouldAddTopSafeAreaMargin,
- shouldAddBottomSafeAreaPadding,
- shouldAddTopSafeAreaPadding,
- modalContainerStyleMarginTop: modalContainerStyle.marginTop,
- modalContainerStyleMarginBottom: modalContainerStyle.marginBottom,
- modalContainerStylePaddingTop: modalContainerStyle.paddingTop,
- modalContainerStylePaddingBottom: modalContainerStyle.paddingBottom,
- insets,
- });
-
- return (
-
- {this.props.children}
-
- );
- }}
-
-
- );
- }
+ {children}
+
+
+ );
}
BaseModal.propTypes = propTypes;
BaseModal.defaultProps = defaultProps;
+BaseModal.displayName = 'BaseModal';
-export default React.forwardRef((props, ref) => (
+export default forwardRef((props, ref) => (