Skip to content
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

Enable React concurrent mode #42592

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
diff --git a/node_modules/@react-navigation/core/lib/module/useNavigationBuilder.js b/node_modules/@react-navigation/core/lib/module/useNavigationBuilder.js
index 051520b..6fb49e0 100644
--- a/node_modules/@react-navigation/core/lib/module/useNavigationBuilder.js
+++ b/node_modules/@react-navigation/core/lib/module/useNavigationBuilder.js
@@ -174,10 +174,6 @@ export default function useNavigationBuilder(createRouter, options) {
getIsInitial
} = React.useContext(NavigationStateContext);
const stateCleanedUp = React.useRef(false);
- const cleanUpState = React.useCallback(() => {
- setCurrentState(undefined);
- stateCleanedUp.current = true;
- }, [setCurrentState]);
const setState = React.useCallback(state => {
if (stateCleanedUp.current) {
// State might have been already cleaned up due to unmount
@@ -291,6 +287,9 @@ export default function useNavigationBuilder(createRouter, options) {
// So we override the state object we return to use the latest state as soon as possible
state = nextState;
React.useEffect(() => {
+ // In strict mode, React will double-invoke effects.
+ // So we need to reset the flag if component was not unmounted
+ stateCleanedUp.current = false;
setKey(navigatorKey);
if (!getIsInitial()) {
// If it's not initial render, we need to update the state
@@ -300,14 +299,10 @@ export default function useNavigationBuilder(createRouter, options) {
}
return () => {
// We need to clean up state for this navigator on unmount
- // We do it in a timeout because we need to detect if another navigator mounted in the meantime
- // For example, if another navigator has started rendering, we should skip cleanup
- // Otherwise, our cleanup step will cleanup state for the other navigator and re-initialize it
- setTimeout(() => {
- if (getCurrentState() !== undefined && getKey() === navigatorKey) {
- cleanUpState();
- }
- }, 0);
+ if (getCurrentState() !== undefined && getKey() === navigatorKey) {
+ setCurrentState(undefined);
+ stateCleanedUp.current = true;
+ }
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
23 changes: 23 additions & 0 deletions patches/react-native-reanimated+3.8.1+003+fix-strict-mode.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
diff --git a/node_modules/react-native-reanimated/lib/module/reanimated2/UpdateProps.js b/node_modules/react-native-reanimated/lib/module/reanimated2/UpdateProps.js
index e69c581..78b7034 100644
--- a/node_modules/react-native-reanimated/lib/module/reanimated2/UpdateProps.js
+++ b/node_modules/react-native-reanimated/lib/module/reanimated2/UpdateProps.js
@@ -7,14 +7,11 @@ import { isFabric, isJest, shouldBeUseWeb } from './PlatformChecker';
import { runOnUIImmediately } from './threads';
let updateProps;
if (shouldBeUseWeb()) {
- updateProps = (_, updates, maybeViewRef, isAnimatedProps) => {
+ updateProps = (viewDescriptorsSet, updates, maybeViewRef, isAnimatedProps) => {
'worklet';
-
- if (maybeViewRef) {
- maybeViewRef.items.forEach((item, _index) => {
- _updatePropsJS(updates, item, isAnimatedProps);
- });
- }
+ viewDescriptorsSet.value.forEach((viewDescriptor) => {
+ _updatePropsJS(updates, {_component: viewDescriptor.tag}, isAnimatedProps);
+ })
};
} else {
updateProps = (viewDescriptors, updates) => {
88 changes: 47 additions & 41 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {CurrentReportIDContextProvider} from './components/withCurrentReportID';
import {EnvironmentProvider} from './components/withEnvironment';
import {KeyboardStateProvider} from './components/withKeyboardState';
import {WindowDimensionsProvider} from './components/withWindowDimensions';
import CONFIG from './CONFIG';
import Expensify from './Expensify';
import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop';
import {ReportIDsContextProvider} from './hooks/useReportIDs';
Expand All @@ -52,51 +53,56 @@ LogBox.ignoreLogs([

const fill = {flex: 1};

const StrictModeWrapper = CONFIG.USE_REACT_STRICT_MODE ? React.StrictMode : ({children}: {children: React.ReactElement}) => children;

function App({url}: AppProps) {
useDefaultDragAndDrop();
OnyxUpdateManager();

return (
<InitialURLContextProvider url={url}>
<GestureHandlerRootView style={fill}>
<ComposeProviders
components={[
OnyxProvider,
ThemeProvider,
ThemeStylesProvider,
ThemeIllustrationsProvider,
SafeAreaProvider,
PortalProvider,
SafeArea,
LocaleContextProvider,
HTMLEngineProvider,
WindowDimensionsProvider,
KeyboardStateProvider,
PopoverContextProvider,
CurrentReportIDContextProvider,
ScrollOffsetContextProvider,
ReportAttachmentsProvider,
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActiveWorkspaceContextProvider,
ReportIDsContextProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
]}
>
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
</ComposeProviders>
</GestureHandlerRootView>
</InitialURLContextProvider>
<StrictModeWrapper>
<InitialURLContextProvider url={url}>
<GestureHandlerRootView style={fill}>
<ComposeProviders
components={[
OnyxProvider,
ThemeProvider,
ThemeStylesProvider,
ThemeIllustrationsProvider,
SafeAreaProvider,
PortalProvider,
SafeArea,
LocaleContextProvider,
HTMLEngineProvider,
WindowDimensionsProvider,
KeyboardStateProvider,
PopoverContextProvider,
CurrentReportIDContextProvider,
ScrollOffsetContextProvider,
ReportAttachmentsProvider,
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActiveWorkspaceContextProvider,
ReportIDsContextProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
]}
>
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
</ComposeProviders>
</GestureHandlerRootView>
</InitialURLContextProvider>
</StrictModeWrapper>
);
}

Expand Down
1 change: 1 addition & 0 deletions src/CONFIG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,5 @@ export default {
IOS_CLIENT_ID: '921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3.apps.googleusercontent.com',
},
GCP_GEOLOCATION_API_KEY: googleGeolocationAPIKey,
USE_REACT_STRICT_MODE: true,
} as const;
1 change: 0 additions & 1 deletion src/setup/platformSetup/index.website.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ const webUpdater = (): PlatformSpecificUpdater => ({
export default function () {
AppRegistry.runApplication(Config.APP_NAME, {
rootTag: document.getElementById('root'),
mode: 'legacy',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming from #47041, our app has some infinite Lottie animations. In concurrent mode, when the SidebarScreen is suspended (i.e., Suspense feature), it conflicts with these animations, preventing the animation component from rendering. :)

});

// When app loads, get current version (production only)
Expand Down
Loading