Skip to content

Commit a58ec07

Browse files
Erica Kleinfacebook-github-bot
Erica Klein
authored andcommitted
Back out "Send Modal onDismiss event on iOS (Fabric) and Android" @bypass-github-export-checks
Summary: ~~Original commit changeset: f419164032c3 Original Phabricator Diff: D52445670 bypass-github-export-checks Changelog: [Internal] Reviewed By: makovkastar Differential Revision: D52932743 fbshipit-source-id: ea37270998213de0ae732477e0fb99b47aae7cd5
1 parent 92b889b commit a58ec07

File tree

17 files changed

+185
-101
lines changed

17 files changed

+185
-101
lines changed

packages/react-native/Libraries/Modal/Modal.d.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ export interface ModalBaseProps {
4343
* The `onShow` prop allows passing a function that will be called once the modal has been shown.
4444
*/
4545
onShow?: ((event: NativeSyntheticEvent<any>) => void) | undefined;
46-
/**
47-
* The `onDismiss` prop allows passing a function that will be called once the modal has been dismissed.
48-
*/
49-
onDismiss?: (() => void) | undefined;
5046
}
5147

5248
export interface ModalPropsIOS {
@@ -74,6 +70,11 @@ export interface ModalPropsIOS {
7470
>
7571
| undefined;
7672

73+
/**
74+
* The `onDismiss` prop allows passing a function that will be called once the modal has been dismissed.
75+
*/
76+
onDismiss?: (() => void) | undefined;
77+
7778
/**
7879
* The `onOrientationChange` callback is called when the orientation changes while the modal is being displayed.
7980
* The orientation provided is only 'portrait' or 'landscape'. This callback is also called on initial render, regardless of the current orientation.

packages/react-native/Libraries/Modal/Modal.js

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import type {ViewProps} from '../Components/View/ViewPropTypes';
1212
import type {RootTag} from '../ReactNative/RootTag';
1313
import type {DirectEventHandler} from '../Types/CodegenTypes';
1414

15+
import NativeEventEmitter from '../EventEmitter/NativeEventEmitter';
16+
import {type EventSubscription} from '../vendor/emitter/EventEmitter';
1517
import ModalInjection from './ModalInjection';
18+
import NativeModalManager from './NativeModalManager';
1619
import RCTModalHostView from './RCTModalHostViewNativeComponent';
1720
import {VirtualizedListContextResetter} from '@react-native/virtualized-lists';
1821

@@ -22,14 +25,34 @@ const AppContainer = require('../ReactNative/AppContainer');
2225
const I18nManager = require('../ReactNative/I18nManager');
2326
const {RootTagContext} = require('../ReactNative/RootTag');
2427
const StyleSheet = require('../StyleSheet/StyleSheet');
28+
const Platform = require('../Utilities/Platform');
2529
const React = require('react');
2630

31+
type ModalEventDefinitions = {
32+
modalDismissed: [{modalID: number}],
33+
};
34+
35+
const ModalEventEmitter =
36+
Platform.OS === 'ios' && NativeModalManager != null
37+
? new NativeEventEmitter<ModalEventDefinitions>(
38+
// T88715063: NativeEventEmitter only used this parameter on iOS. Now it uses it on all platforms, so this code was modified automatically to preserve its behavior
39+
// If you want to use the native module on other platforms, please remove this condition and test its behavior
40+
Platform.OS !== 'ios' ? null : NativeModalManager,
41+
)
42+
: null;
43+
2744
/**
2845
* The Modal component is a simple way to present content above an enclosing view.
2946
*
3047
* See https://reactnative.dev/docs/modal
3148
*/
3249

50+
// In order to route onDismiss callbacks, we need to uniquely identifier each
51+
// <Modal> on screen. There can be different ones, either nested or as siblings.
52+
// We cannot pass the onDismiss callback to native as the view will be
53+
// destroyed before the callback is fired.
54+
let uniqueModalIdentifier = 0;
55+
3356
type OrientationChangeEvent = $ReadOnly<{|
3457
orientation: 'portrait' | 'landscape',
3558
|}>;
@@ -136,10 +159,6 @@ export type Props = $ReadOnly<{|
136159
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
137160
|}>;
138161

139-
type State = {|
140-
isRendering: boolean,
141-
|};
142-
143162
function confirmProps(props: Props) {
144163
if (__DEV__) {
145164
if (
@@ -154,35 +173,53 @@ function confirmProps(props: Props) {
154173
}
155174
}
156175

157-
class Modal extends React.Component<Props, State> {
176+
class Modal extends React.Component<Props> {
158177
static defaultProps: {|hardwareAccelerated: boolean, visible: boolean|} = {
159178
visible: true,
160179
hardwareAccelerated: false,
161180
};
162181

163182
static contextType: React.Context<RootTag> = RootTagContext;
164183

184+
_identifier: number;
185+
_eventSubscription: ?EventSubscription;
186+
165187
constructor(props: Props) {
166188
super(props);
167-
this.state = {
168-
isRendering: props.visible === true,
169-
};
170189
if (__DEV__) {
171190
confirmProps(props);
172191
}
192+
this._identifier = uniqueModalIdentifier++;
173193
}
174194

175-
componentDidUpdate(prevProps: Props) {
176-
if (prevProps.visible !== true && this.props.visible === true) {
177-
this.setState({isRendering: true});
195+
componentDidMount() {
196+
// 'modalDismissed' is for the old renderer in iOS only
197+
if (ModalEventEmitter) {
198+
this._eventSubscription = ModalEventEmitter.addListener(
199+
'modalDismissed',
200+
event => {
201+
if (event.modalID === this._identifier && this.props.onDismiss) {
202+
this.props.onDismiss();
203+
}
204+
},
205+
);
206+
}
207+
}
208+
209+
componentWillUnmount() {
210+
if (this._eventSubscription) {
211+
this._eventSubscription.remove();
178212
}
213+
}
214+
215+
componentDidUpdate() {
179216
if (__DEV__) {
180217
confirmProps(this.props);
181218
}
182219
}
183220

184221
render(): React.Node {
185-
if (this.props.visible !== true && !this.state.isRendering) {
222+
if (this.props.visible !== true) {
186223
return null;
187224
}
188225

@@ -216,14 +253,13 @@ class Modal extends React.Component<Props, State> {
216253
onRequestClose={this.props.onRequestClose}
217254
onShow={this.props.onShow}
218255
onDismiss={() => {
219-
this.setState({isRendering: false}, () => {
220-
if (this.props.onDismiss) {
221-
this.props.onDismiss();
222-
}
223-
});
256+
if (this.props.onDismiss) {
257+
this.props.onDismiss();
258+
}
224259
}}
225260
visible={this.props.visible}
226261
statusBarTranslucent={this.props.statusBarTranslucent}
262+
identifier={this._identifier}
227263
style={styles.modal}
228264
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
229265
onStartShouldSetResponder={this._shouldSetResponder}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict
8+
* @format
9+
*/
10+
11+
import type {TurboModule} from '../TurboModule/RCTExport';
12+
13+
import * as TurboModuleRegistry from '../TurboModule/TurboModuleRegistry';
14+
15+
export interface Spec extends TurboModule {
16+
// RCTEventEmitter
17+
+addListener: (eventName: string) => void;
18+
+removeListeners: (count: number) => void;
19+
}
20+
21+
export default (TurboModuleRegistry.get<Spec>('ModalManager'): ?Spec);

packages/react-native/Libraries/Modal/RCTModalHostViewNativeComponent.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010

1111
import type {ViewProps} from '../Components/View/ViewPropTypes';
1212
import type {HostComponent} from '../Renderer/shims/ReactNativeTypes';
13-
import type {DirectEventHandler, WithDefault} from '../Types/CodegenTypes';
13+
import type {
14+
DirectEventHandler,
15+
Int32,
16+
WithDefault,
17+
} from '../Types/CodegenTypes';
1418

1519
import codegenNativeComponent from '../Utilities/codegenNativeComponent';
1620

@@ -122,6 +126,11 @@ type NativeProps = $ReadOnly<{|
122126
* See https://reactnative.dev/docs/modal#onorientationchange
123127
*/
124128
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
129+
130+
/**
131+
* The `identifier` is the unique number for identifying Modal components.
132+
*/
133+
identifier?: WithDefault<Int32, 0>,
125134
|}>;
126135

127136
export default (codegenNativeComponent<NativeProps>('ModalHostView', {

packages/react-native/Libraries/Modal/__tests__/__snapshots__/Modal-test.js.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exports[`<Modal /> should render as <RCTModalHostView> when not mocked 1`] = `
1313
<RCTModalHostView
1414
animationType="none"
1515
hardwareAccelerated={false}
16+
identifier={3}
1617
onDismiss={[Function]}
1718
onStartShouldSetResponder={[Function]}
1819
presentationStyle="fullScreen"

packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6449,6 +6449,15 @@ exports[`public API should not change unintentionally Libraries/Modal/ModalInjec
64496449
"
64506450
`;
64516451

6452+
exports[`public API should not change unintentionally Libraries/Modal/NativeModalManager.js 1`] = `
6453+
"export interface Spec extends TurboModule {
6454+
+addListener: (eventName: string) => void;
6455+
+removeListeners: (count: number) => void;
6456+
}
6457+
declare export default ?Spec;
6458+
"
6459+
`;
6460+
64526461
exports[`public API should not change unintentionally Libraries/Modal/RCTModalHostViewNativeComponent.js 1`] = `
64536462
"type OrientationChangeEvent = $ReadOnly<{|
64546463
orientation: \\"portrait\\" | \\"landscape\\",
@@ -6479,6 +6488,7 @@ type NativeProps = $ReadOnly<{|
64796488
\\"portrait\\",
64806489
>,
64816490
onOrientationChange?: ?DirectEventHandler<OrientationChangeEvent>,
6491+
identifier?: WithDefault<Int32, 0>,
64826492
|}>;
64836493
declare export default HostComponent<NativeProps>;
64846494
"

packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#import "RCTModalHostViewComponentView.h"
99

1010
#import <React/RCTBridge+Private.h>
11+
#import <React/RCTModalManager.h>
1112
#import <React/UIView+React.h>
1213
#import <react/renderer/components/modal/ModalHostViewComponentDescriptor.h>
1314
#import <react/renderer/components/modal/ModalHostViewState.h>

packages/react-native/React/Views/RCTModalHostView.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,23 @@
2323
@property (nonatomic, assign, getter=isTransparent) BOOL transparent;
2424

2525
@property (nonatomic, copy) RCTDirectEventBlock onShow;
26-
@property (nonatomic, copy) RCTDirectEventBlock onDismiss;
2726
@property (nonatomic, assign) BOOL visible;
2827

2928
// Android only
3029
@property (nonatomic, assign) BOOL statusBarTranslucent;
3130
@property (nonatomic, assign) BOOL hardwareAccelerated;
3231
@property (nonatomic, assign) BOOL animated;
3332

33+
@property (nonatomic, copy) NSNumber *identifier;
34+
3435
@property (nonatomic, weak) id<RCTModalHostViewInteractor> delegate;
3536

3637
@property (nonatomic, copy) NSArray<NSString *> *supportedOrientations;
3738
@property (nonatomic, copy) RCTDirectEventBlock onOrientationChange;
3839

40+
// Fabric only
41+
@property (nonatomic, copy) RCTDirectEventBlock onDismiss;
42+
3943
- (instancetype)initWithBridge:(RCTBridge *)bridge NS_DESIGNATED_INITIALIZER;
4044

4145
@end

packages/react-native/React/Views/RCTModalHostViewManager.m

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import "RCTBridge.h"
1111
#import "RCTModalHostView.h"
1212
#import "RCTModalHostViewController.h"
13+
#import "RCTModalManager.h"
1314
#import "RCTShadowView.h"
1415
#import "RCTUtils.h"
1516

@@ -90,8 +91,8 @@ - (void)dismissModalHostView:(RCTModalHostView *)modalHostView
9091
animated:(BOOL)animated
9192
{
9293
dispatch_block_t completionBlock = ^{
93-
if (modalHostView.onDismiss) {
94-
modalHostView.onDismiss(nil);
94+
if (modalHostView.identifier) {
95+
[[self.bridge moduleForClass:[RCTModalManager class]] modalDismissed:modalHostView.identifier];
9596
}
9697
};
9798
dispatch_async(dispatch_get_main_queue(), ^{
@@ -123,10 +124,13 @@ - (void)invalidate
123124
RCT_EXPORT_VIEW_PROPERTY(hardwareAccelerated, BOOL)
124125
RCT_EXPORT_VIEW_PROPERTY(animated, BOOL)
125126
RCT_EXPORT_VIEW_PROPERTY(onShow, RCTDirectEventBlock)
126-
RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock)
127+
RCT_EXPORT_VIEW_PROPERTY(identifier, NSNumber)
127128
RCT_EXPORT_VIEW_PROPERTY(supportedOrientations, NSArray)
128129
RCT_EXPORT_VIEW_PROPERTY(onOrientationChange, RCTDirectEventBlock)
129130
RCT_EXPORT_VIEW_PROPERTY(visible, BOOL)
130131
RCT_EXPORT_VIEW_PROPERTY(onRequestClose, RCTDirectEventBlock)
131132

133+
// Fabric only
134+
RCT_EXPORT_VIEW_PROPERTY(onDismiss, RCTDirectEventBlock)
135+
132136
@end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import <UIKit/UIKit.h>
9+
10+
#import <React/RCTBridgeModule.h>
11+
#import <React/RCTEventEmitter.h>
12+
13+
@interface RCTModalManager : RCTEventEmitter <RCTBridgeModule>
14+
15+
- (void)modalDismissed:(NSNumber *)modalID;
16+
17+
@end
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#import "RCTModalManager.h"
9+
10+
@interface RCTModalManager ()
11+
12+
@property BOOL shouldEmit;
13+
14+
@end
15+
16+
@implementation RCTModalManager
17+
18+
RCT_EXPORT_MODULE();
19+
20+
- (NSArray<NSString *> *)supportedEvents
21+
{
22+
return @[ @"modalDismissed" ];
23+
}
24+
25+
- (void)startObserving
26+
{
27+
_shouldEmit = YES;
28+
}
29+
30+
- (void)stopObserving
31+
{
32+
_shouldEmit = NO;
33+
}
34+
35+
- (void)modalDismissed:(NSNumber *)modalID
36+
{
37+
if (_shouldEmit) {
38+
[self sendEventWithName:@"modalDismissed" body:@{@"modalID" : modalID}];
39+
}
40+
}
41+
42+
@end

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5776,6 +5776,7 @@ public abstract interface class com/facebook/react/viewmanagers/ModalHostViewMan
57765776
public abstract fun setAnimated (Landroid/view/View;Z)V
57775777
public abstract fun setAnimationType (Landroid/view/View;Ljava/lang/String;)V
57785778
public abstract fun setHardwareAccelerated (Landroid/view/View;Z)V
5779+
public abstract fun setIdentifier (Landroid/view/View;I)V
57795780
public abstract fun setPresentationStyle (Landroid/view/View;Ljava/lang/String;)V
57805781
public abstract fun setStatusBarTranslucent (Landroid/view/View;Z)V
57815782
public abstract fun setSupportedOrientations (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;)V
@@ -6118,6 +6119,8 @@ public class com/facebook/react/views/modal/ReactModalHostManager : com/facebook
61186119
public fun setAnimationType (Lcom/facebook/react/views/modal/ReactModalHostView;Ljava/lang/String;)V
61196120
public synthetic fun setHardwareAccelerated (Landroid/view/View;Z)V
61206121
public fun setHardwareAccelerated (Lcom/facebook/react/views/modal/ReactModalHostView;Z)V
6122+
public synthetic fun setIdentifier (Landroid/view/View;I)V
6123+
public fun setIdentifier (Lcom/facebook/react/views/modal/ReactModalHostView;I)V
61216124
public synthetic fun setPresentationStyle (Landroid/view/View;Ljava/lang/String;)V
61226125
public fun setPresentationStyle (Lcom/facebook/react/views/modal/ReactModalHostView;Ljava/lang/String;)V
61236126
public synthetic fun setStatusBarTranslucent (Landroid/view/View;Z)V
@@ -6158,14 +6161,11 @@ public class com/facebook/react/views/modal/ReactModalHostView : android/view/Vi
61586161
public fun removeViewAt (I)V
61596162
protected fun setAnimationType (Ljava/lang/String;)V
61606163
protected fun setHardwareAccelerated (Z)V
6161-
protected fun setOnDismissListener (Landroid/content/DialogInterface$OnDismissListener;)V
61626164
protected fun setOnRequestCloseListener (Lcom/facebook/react/views/modal/ReactModalHostView$OnRequestCloseListener;)V
61636165
protected fun setOnShowListener (Landroid/content/DialogInterface$OnShowListener;)V
61646166
public fun setStateWrapper (Lcom/facebook/react/uimanager/StateWrapper;)V
61656167
protected fun setStatusBarTranslucent (Z)V
61666168
protected fun setTransparent (Z)V
6167-
protected fun setVisible (Z)V
6168-
protected fun showOrDismiss ()V
61696169
protected fun showOrUpdate ()V
61706170
public fun updateState (II)V
61716171
}

0 commit comments

Comments
 (0)