-
Notifications
You must be signed in to change notification settings - Fork 3.7k
perf: Extract DeepLinkHandler from Expensify.tsx into its own component #83196
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
38ddaa1
db2b02b
5730c3a
151e6b4
fe1b64d
75feae7
be320b3
9d0b312
8589aa6
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 |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import {useEffect, useRef} from 'react'; | ||
| import type {NativeEventSubscription} from 'react-native'; | ||
| import {Linking} from 'react-native'; | ||
| import CONST from './CONST'; | ||
| import useIsAuthenticated from './hooks/useIsAuthenticated'; | ||
| import useOnyx from './hooks/useOnyx'; | ||
| import {openReportFromDeepLink} from './libs/actions/Link'; | ||
| import * as Report from './libs/actions/Report'; | ||
| import {hasAuthToken} from './libs/actions/Session'; | ||
| import Log from './libs/Log'; | ||
| import {endSpan} from './libs/telemetry/activeSpans'; | ||
| import ONYXKEYS from './ONYXKEYS'; | ||
| import type {Route} from './ROUTES'; | ||
| import isLoadingOnyxValue from './types/utils/isLoadingOnyxValue'; | ||
|
|
||
| type DeepLinkHandlerProps = { | ||
| /** Callback to set the initial URL resolved from deep linking */ | ||
| onInitialUrl: (url: Route | null) => void; | ||
| }; | ||
|
|
||
| /** | ||
| * Component that does not render anything but isolates the COLLECTION.REPORT Onyx subscription | ||
| * from the root Expensify component to prevent cascading re-renders of the | ||
| * entire navigation tree on every report change. | ||
| */ | ||
| function DeepLinkHandler({onInitialUrl}: DeepLinkHandlerProps) { | ||
| const linkingChangeListener = useRef<NativeEventSubscription | null>(null); | ||
|
|
||
| const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); | ||
| const [, sessionMetadata] = useOnyx(ONYXKEYS.SESSION); | ||
| const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); | ||
| const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); | ||
| const isAuthenticated = useIsAuthenticated(); | ||
|
|
||
| useEffect(() => { | ||
| if (isLoadingOnyxValue(sessionMetadata)) { | ||
| return; | ||
| } | ||
| // If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report | ||
| Linking.getInitialURL().then((url) => { | ||
| onInitialUrl(url as Route); | ||
|
|
||
| if (url) { | ||
| if (conciergeReportID === undefined) { | ||
| Log.info('[Deep link] conciergeReportID is undefined when processing initial URL', false, {url}); | ||
| } | ||
| if (introSelected === undefined) { | ||
| Log.info('[Deep link] introSelected is undefined when processing initial URL', false, {url}); | ||
| } | ||
| openReportFromDeepLink(url, allReports, isAuthenticated, conciergeReportID, introSelected); | ||
| } else { | ||
| Report.doneCheckingPublicRoom(); | ||
| } | ||
|
|
||
| endSpan(CONST.TELEMETRY.SPAN_BOOTSPLASH.DEEP_LINK); | ||
| }); | ||
|
|
||
| // Open chat report from a deep link (only mobile native) | ||
| linkingChangeListener.current = Linking.addEventListener('url', (state) => { | ||
| if (conciergeReportID === undefined) { | ||
| Log.info('[Deep link] conciergeReportID is undefined when processing URL change', false, {url: state.url}); | ||
| } | ||
| if (introSelected === undefined) { | ||
| Log.info('[Deep link] introSelected is undefined when processing URL change', false, {url: state.url}); | ||
| } | ||
| const isCurrentlyAuthenticated = hasAuthToken(); | ||
| openReportFromDeepLink(state.url, allReports, isCurrentlyAuthenticated, conciergeReportID, introSelected); | ||
| }); | ||
|
|
||
| return () => { | ||
| linkingChangeListener.current?.remove(); | ||
| }; | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps -- we only want this effect to re-run when conciergeReportID changes | ||
| }, [sessionMetadata?.status, conciergeReportID, introSelected]); | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| export default DeepLinkHandler; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ import HybridAppModule from '@expensify/react-native-hybrid-app'; | |
| import * as Sentry from '@sentry/react-native'; | ||
| import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from 'react'; | ||
| import type {NativeEventSubscription} from 'react-native'; | ||
| import {AppState, Linking, Platform} from 'react-native'; | ||
| import {AppState, Platform} from 'react-native'; | ||
| import type {OnyxEntry} from 'react-native-onyx'; | ||
| import Onyx from 'react-native-onyx'; | ||
| import ConfirmModal from './components/ConfirmModal'; | ||
|
|
@@ -16,6 +16,7 @@ import SplashScreenHider from './components/SplashScreenHider'; | |
| import UpdateAppModal from './components/UpdateAppModal'; | ||
| import CONFIG from './CONFIG'; | ||
| import CONST from './CONST'; | ||
| import DeepLinkHandler from './DeepLinkHandler'; | ||
| import useDebugShortcut from './hooks/useDebugShortcut'; | ||
| import useIsAuthenticated from './hooks/useIsAuthenticated'; | ||
| import useLocalize from './hooks/useLocalize'; | ||
|
|
@@ -25,11 +26,8 @@ import usePriorityMode from './hooks/usePriorityChange'; | |
| import {confirmReadyToOpenApp, openApp, updateLastRoute} from './libs/actions/App'; | ||
| import {disconnect} from './libs/actions/Delegate'; | ||
| import * as EmojiPickerAction from './libs/actions/EmojiPickerAction'; | ||
| import {openReportFromDeepLink} from './libs/actions/Link'; | ||
| // This lib needs to be imported, but it has nothing to export since all it contains is an Onyx connection | ||
| import './libs/actions/replaceOptimisticReportWithActualReport'; | ||
| import * as Report from './libs/actions/Report'; | ||
| import {hasAuthToken} from './libs/actions/Session'; | ||
| import * as User from './libs/actions/User'; | ||
| import * as ActiveClientManager from './libs/ActiveClientManager'; | ||
| import {isSafari} from './libs/Browser'; | ||
|
|
@@ -55,7 +53,6 @@ import * as ReportActionContextMenu from './pages/inbox/report/ContextMenu/Repor | |
| import type {Route} from './ROUTES'; | ||
| import {useSplashScreenActions, useSplashScreenState} from './SplashScreenStateContext'; | ||
| import type {ScreenShareRequest} from './types/onyx'; | ||
| import isLoadingOnyxValue from './types/utils/isLoadingOnyxValue'; | ||
|
|
||
| Onyx.registerLogger(({level, message, parameters}) => { | ||
| if (level === 'alert') { | ||
|
|
@@ -89,7 +86,6 @@ type ExpensifyProps = { | |
| }; | ||
| function Expensify() { | ||
| const appStateChangeListener = useRef<NativeEventSubscription | null>(null); | ||
| const linkingChangeListener = useRef<NativeEventSubscription | null>(null); | ||
| const hasLoggedDelegateMismatchRef = useRef(false); | ||
| const hasHandledMissingIsLoadingAppRef = useRef(false); | ||
| const [isNavigationReady, setIsNavigationReady] = useState(false); | ||
|
|
@@ -99,7 +95,7 @@ function Expensify() { | |
| const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false); | ||
| const {translate, preferredLocale} = useLocalize(); | ||
| const [account] = useOnyx(ONYXKEYS.ACCOUNT); | ||
| const [session, sessionMetadata] = useOnyx(ONYXKEYS.SESSION); | ||
| const [session] = useOnyx(ONYXKEYS.SESSION); | ||
| const [lastRoute] = useOnyx(ONYXKEYS.LAST_ROUTE); | ||
| const [userMetadata] = useOnyx(ONYXKEYS.USER_METADATA); | ||
| const [isCheckingPublicRoom = true] = useOnyx(ONYXKEYS.IS_CHECKING_PUBLIC_ROOM, {initWithStoredValues: false}); | ||
|
|
@@ -108,14 +104,11 @@ function Expensify() { | |
| const [isSidebarLoaded] = useOnyx(ONYXKEYS.IS_SIDEBAR_LOADED); | ||
| const [screenShareRequest] = useOnyx(ONYXKEYS.SCREEN_SHARE_REQUEST); | ||
| const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH); | ||
| const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); | ||
| const [hasLoadedApp] = useOnyx(ONYXKEYS.HAS_LOADED_APP); | ||
| const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); | ||
| const {isOffline} = useNetwork(); | ||
| const [stashedCredentials = CONST.EMPTY_OBJECT] = useOnyx(ONYXKEYS.STASHED_CREDENTIALS); | ||
| const [stashedSession] = useOnyx(ONYXKEYS.STASHED_SESSION); | ||
| const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); | ||
| const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); | ||
|
|
||
| useDebugShortcut(); | ||
| usePriorityMode(); | ||
|
|
@@ -321,47 +314,6 @@ function Expensify() { | |
| // eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want this effect to run again | ||
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| if (isLoadingOnyxValue(sessionMetadata)) { | ||
| return; | ||
| } | ||
| // If the app is opened from a deep link, get the reportID (if exists) from the deep link and navigate to the chat report | ||
| Linking.getInitialURL().then((url) => { | ||
| setInitialUrl(url as Route); | ||
|
|
||
| if (url) { | ||
| if (conciergeReportID === undefined) { | ||
| Log.info('[Deep link] conciergeReportID is undefined when processing initial URL', false, {url}); | ||
| } | ||
| if (introSelected === undefined) { | ||
| Log.info('[Deep link] introSelected is undefined when processing initial URL', false, {url}); | ||
| } | ||
| openReportFromDeepLink(url, allReports, isAuthenticated, conciergeReportID, introSelected); | ||
| } else { | ||
| Report.doneCheckingPublicRoom(); | ||
| } | ||
|
|
||
| endSpan(CONST.TELEMETRY.SPAN_BOOTSPLASH.DEEP_LINK); | ||
| }); | ||
|
|
||
| // Open chat report from a deep link (only mobile native) | ||
| linkingChangeListener.current = Linking.addEventListener('url', (state) => { | ||
| if (conciergeReportID === undefined) { | ||
| Log.info('[Deep link] conciergeReportID is undefined when processing URL change', false, {url: state.url}); | ||
| } | ||
| if (introSelected === undefined) { | ||
| Log.info('[Deep link] introSelected is undefined when processing URL change', false, {url: state.url}); | ||
| } | ||
| const isCurrentlyAuthenticated = hasAuthToken(); | ||
| openReportFromDeepLink(state.url, allReports, isCurrentlyAuthenticated, conciergeReportID, introSelected); | ||
| }); | ||
|
|
||
| return () => { | ||
| linkingChangeListener.current?.remove(); | ||
| }; | ||
| // eslint-disable-next-line react-hooks/exhaustive-deps -- we only want this effect to re-run when conciergeReportID changes | ||
| }, [sessionMetadata?.status, conciergeReportID, introSelected]); | ||
|
|
||
| useLayoutEffect(() => { | ||
| if (!isNavigationReady || !lastRoute) { | ||
| return; | ||
|
|
@@ -449,6 +401,7 @@ function Expensify() { | |
| </> | ||
| )} | ||
|
|
||
| <DeepLinkHandler onInitialUrl={setInitialUrl} /> | ||
|
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.
Placing Useful? React with 👍 / 👎.
Contributor
Author
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. I'll can into this one. @mountiny do you think we should do a QA instead to make sure we can await the migrations safely?
Contributor
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. Yeah I think its better to add some QA even if it should be safe, better safe than sorry
Contributor
Author
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. Deeplinking still needs access to Onyx data, and this
I find this invalid, migration was and remains async, the span start in the same place. It's true it might take just a big longer to finish this span now. |
||
| <AppleAuthWrapper /> | ||
| {hasAttemptedToOpenPublicRoom && ( | ||
| <NavigationRoot | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.