diff --git a/packages/react-native/Libraries/Inspector/Inspector.js b/packages/react-native/Libraries/Inspector/Inspector.js index bc76e794870..330b501b0a7 100644 --- a/packages/react-native/Libraries/Inspector/Inspector.js +++ b/packages/react-native/Libraries/Inspector/Inspector.js @@ -29,13 +29,17 @@ const hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; // Required for React DevTools to view/edit React Native styles in Flipper. // Flipper doesn't inject these values when initializing DevTools. -hook.resolveRNStyle = require('../StyleSheet/flattenStyle'); -hook.nativeStyleEditorValidAttributes = Object.keys(ReactNativeStyleAttributes); +if (hook) { + hook.resolveRNStyle = require('../StyleSheet/flattenStyle'); + hook.nativeStyleEditorValidAttributes = Object.keys( + ReactNativeStyleAttributes, + ); +} class Inspector extends React.Component< { inspectedView: ?HostRef, - onRequestRerenderApp: (callback: (instance: ?HostRef) => void) => void, + onRequestRerenderApp: () => void, ... }, { @@ -194,9 +198,7 @@ class Inspector extends React.Component< setTouchTargeting(val: boolean) { PressabilityDebug.setEnabled(val); - this.props.onRequestRerenderApp(inspectedView => { - this.setState({inspectedView}); - }); + this.props.onRequestRerenderApp(); } setNetworking(val: boolean) { diff --git a/packages/react-native/Libraries/ReactNative/AppContainer-dev.js b/packages/react-native/Libraries/ReactNative/AppContainer-dev.js new file mode 100644 index 00000000000..7cced77b069 --- /dev/null +++ b/packages/react-native/Libraries/ReactNative/AppContainer-dev.js @@ -0,0 +1,154 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import type {Props} from './AppContainer'; + +import TraceUpdateOverlay from '../Components/TraceUpdateOverlay/TraceUpdateOverlay'; +import View from '../Components/View/View'; +import ViewNativeComponent from '../Components/View/ViewNativeComponent'; +import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter'; +import ReactDevToolsOverlay from '../Inspector/DevtoolsOverlay'; +import LogBoxNotificationContainer from '../LogBox/LogBoxNotificationContainer'; +import StyleSheet from '../StyleSheet/StyleSheet'; +import {RootTagContext, createRootTag} from './RootTag'; +import * as React from 'react'; + +const {useEffect, useState, useCallback} = React; + +const reactDevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; + +type InspectorDeferredProps = { + inspectedView: React.ElementRef | null, + onInspectedViewRerenderRequest: () => void, +}; + +const InspectorDeferred = ({ + inspectedView, + onInspectedViewRerenderRequest, +}: InspectorDeferredProps) => { + // D39382967 adds a require cycle: InitializeCore -> AppContainer -> Inspector -> InspectorPanel -> ScrollView -> InitializeCore + // We can't remove it yet, fallback to dynamic require for now. This is the only reason why this logic is in a separate function. + const Inspector = require('../Inspector/Inspector'); + + return ( + + ); +}; + +const AppContainer = ({ + children, + fabric, + initialProps, + internal_excludeInspector = false, + internal_excludeLogBox = false, + rootTag, + showArchitectureIndicator, + WrapperComponent, +}: Props): React.Node => { + const [mainRef, setMainRef] = useState | null>(null); + + const [key, setKey] = useState(0); + const [shouldRenderInspector, setShouldRenderInspector] = useState(false); + const [shouldRenderDebuggingOverlays, setShouldRenderDebuggingOverlays] = + useState(reactDevToolsHook?.reactDevtoolsAgent != null); + + useEffect(() => { + let inspectorSubscription = null; + if (!internal_excludeInspector) { + inspectorSubscription = RCTDeviceEventEmitter.addListener( + 'toggleElementInspector', + () => setShouldRenderInspector(value => !value), + ); + } + + let reactDevToolsAgentListener = null; + // Subscribe listener, if agent is not attached yet + if ( + reactDevToolsHook != null && + reactDevToolsHook.reactDevtoolsAgent == null + ) { + reactDevToolsAgentListener = () => setShouldRenderDebuggingOverlays(true); + reactDevToolsHook.on?.('react-devtools', reactDevToolsAgentListener); + } + + return () => { + inspectorSubscription?.remove(); + + if ( + reactDevToolsHook?.off != null && + reactDevToolsAgentListener != null + ) { + reactDevToolsHook.off('react-devtools', reactDevToolsAgentListener); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + let innerView: React.Node = ( + + {children} + + ); + + if (WrapperComponent != null) { + innerView = ( + + {innerView} + + ); + } + + const onInspectedViewRerenderRequest = useCallback( + () => setKey(k => k + 1), + [], + ); + + return ( + + + {innerView} + + {shouldRenderDebuggingOverlays && } + {shouldRenderDebuggingOverlays && ( + + )} + + {shouldRenderInspector && ( + + )} + + {!internal_excludeLogBox && } + + + ); +}; + +const styles = StyleSheet.create({ + container: {flex: 1}, +}); + +export default AppContainer; diff --git a/packages/react-native/Libraries/ReactNative/AppContainer-prod.js b/packages/react-native/Libraries/ReactNative/AppContainer-prod.js new file mode 100644 index 00000000000..201d54562b9 --- /dev/null +++ b/packages/react-native/Libraries/ReactNative/AppContainer-prod.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict-local + * @format + * @oncall react_native + */ + +import type {Props} from './AppContainer'; + +import View from '../Components/View/View'; +import StyleSheet from '../StyleSheet/StyleSheet'; +import {RootTagContext, createRootTag} from './RootTag'; +import * as React from 'react'; + +const AppContainer = ({ + children, + fabric, + initialProps, + rootTag, + showArchitectureIndicator, + WrapperComponent, +}: Props): React.Node => { + let innerView = children; + + if (WrapperComponent != null) { + innerView = ( + + {innerView} + + ); + } + + return ( + + + {innerView} + + + ); +}; + +const styles = StyleSheet.create({ + root: {flex: 1}, +}); + +export default AppContainer; diff --git a/packages/react-native/Libraries/ReactNative/AppContainer.js b/packages/react-native/Libraries/ReactNative/AppContainer.js index 6ad1aa217ab..b27db96888b 100644 --- a/packages/react-native/Libraries/ReactNative/AppContainer.js +++ b/packages/react-native/Libraries/ReactNative/AppContainer.js @@ -10,170 +10,21 @@ import type {RootTag} from './RootTag'; -import View from '../Components/View/View'; -import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter'; -import StyleSheet from '../StyleSheet/StyleSheet'; -import {type EventSubscription} from '../vendor/emitter/EventEmitter'; -import {RootTagContext, createRootTag} from './RootTag'; import * as React from 'react'; -const reactDevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__; - -type Props = $ReadOnly<{| +export type Props = $ReadOnly<{| children?: React.Node, fabric?: boolean, rootTag: number | RootTag, initialProps?: {...}, showArchitectureIndicator?: boolean, WrapperComponent?: ?React.ComponentType, - internal_excludeLogBox?: ?boolean, - internal_excludeInspector?: ?boolean, + internal_excludeLogBox?: boolean, + internal_excludeInspector?: boolean, |}>; -type State = {| - inspector: ?React.Node, - devtoolsOverlay: ?React.Node, - traceUpdateOverlay: ?React.Node, - mainKey: number, -|}; - -class AppContainer extends React.Component { - state: State = { - inspector: null, - devtoolsOverlay: null, - traceUpdateOverlay: null, - mainKey: 1, - }; - _mainRef: ?React.ElementRef; - _subscription: ?EventSubscription = null; - _reactDevToolsAgentListener: ?() => void = null; - - static getDerivedStateFromError: any = undefined; - - mountReactDevToolsOverlays(): void { - if (__DEV__) { - const DevtoolsOverlay = require('../Inspector/DevtoolsOverlay').default; - const devtoolsOverlay = ; - - const TraceUpdateOverlay = - require('../Components/TraceUpdateOverlay/TraceUpdateOverlay').default; - const traceUpdateOverlay = ; - - this.setState({devtoolsOverlay, traceUpdateOverlay}); - } - } - - componentDidMount(): void { - if (__DEV__) { - if (!this.props.internal_excludeInspector) { - this._subscription = RCTDeviceEventEmitter.addListener( - 'toggleElementInspector', - () => { - const Inspector = require('../Inspector/Inspector'); - const inspector = this.state.inspector ? null : ( - { - this.setState( - s => ({mainKey: s.mainKey + 1}), - () => updateInspectedView(this._mainRef), - ); - }} - /> - ); - this.setState({inspector}); - }, - ); - - if (reactDevToolsHook != null) { - if (reactDevToolsHook.reactDevtoolsAgent) { - // In case if this is not the first AppContainer rendered and React DevTools are already attached - this.mountReactDevToolsOverlays(); - return; - } - - this._reactDevToolsAgentListener = () => - this.mountReactDevToolsOverlays(); - - if (reactDevToolsHook.on != null) { - reactDevToolsHook.on( - 'react-devtools', - this._reactDevToolsAgentListener, - ); - } - } - } - } - } - - componentWillUnmount(): void { - if (this._subscription != null) { - this._subscription.remove(); - } - - if ( - reactDevToolsHook?.off != null && - this._reactDevToolsAgentListener != null - ) { - reactDevToolsHook.off('react-devtools', this._reactDevToolsAgentListener); - } - } - - render(): React.Node { - let logBox = null; - if (__DEV__) { - if (!this.props.internal_excludeLogBox) { - const LogBoxNotificationContainer = - require('../LogBox/LogBoxNotificationContainer').default; - logBox = ; - } - } - - let innerView: React.Node = ( - { - this._mainRef = ref; - }}> - {this.props.children} - - ); - - const Wrapper = this.props.WrapperComponent; - if (Wrapper != null) { - innerView = ( - - {innerView} - - ); - } - - return ( - - - {innerView} - {this.state.traceUpdateOverlay} - {this.state.devtoolsOverlay} - {this.state.inspector} - {logBox} - - - ); - } -} - -const styles = StyleSheet.create({ - appContainer: { - flex: 1, - }, -}); +const AppContainer: React.AbstractComponent = __DEV__ + ? require('./AppContainer-dev').default + : require('./AppContainer-prod').default; module.exports = AppContainer;