Skip to content

Commit 80366f6

Browse files
authored
Merge pull request Expensify#89908 from callstack-internal/perf/workspaces-tab-button-rerenders
perf: stop WorkspacesTabButton re-rendering on every navigation
2 parents b8424b9 + 45fedfd commit 80366f6

2 files changed

Lines changed: 135 additions & 84 deletions

File tree

Lines changed: 56 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
import {useCallback} from 'react';
2-
import type {OnyxCollection} from 'react-native-onyx';
31
import {getPreservedNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState';
42
import {isFullScreenName, isWorkspaceNavigatorRouteName} from '@libs/Navigation/helpers/isNavigatorName';
53
import {getWorkspacesTabStateFromSessionStorage} from '@libs/Navigation/helpers/lastVisitedTabPathUtils';
64
import navigateToWorkspacesPage from '@libs/Navigation/helpers/navigateToWorkspacesPage';
75
import {getTabState} from '@libs/Navigation/helpers/tabNavigatorUtils';
6+
import navigationRef from '@libs/Navigation/navigationRef';
87
import type {DomainSplitNavigatorParamList, WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types';
98
import NAVIGATORS from '@src/NAVIGATORS';
109
import ONYXKEYS from '@src/ONYXKEYS';
1110
import type SCREENS from '@src/SCREENS';
12-
import type {Domain, Policy} from '@src/types/onyx';
1311
import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
1412
import useOnyx from './useOnyx';
1513
import useResponsiveLayout from './useResponsiveLayout';
16-
import useRootNavigationState from './useRootNavigationState';
1714

1815
/**
1916
* The Workspaces tab can show three things: the workspaces list, a specific workspace page,
@@ -22,82 +19,73 @@ import useRootNavigationState from './useRootNavigationState';
2219
*
2320
* It resolves the last visited route from navigation state, fetches the matching policy/domain
2421
* from Onyx (to verify it's still accessible), and returns a callback that performs the navigation.
22+
*
23+
* Nav state is resolved at click time so the hook has no reactive subscriptions to navigation
24+
* — unrelated navigations (e.g. opening a report) don't trigger re-renders.
2525
*/
2626
function useRestoreWorkspacesTabOnNavigate() {
2727
const {shouldUseNarrowLayout} = useResponsiveLayout();
2828
const {login: currentUserLogin} = useCurrentUserPersonalDetails();
29+
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
30+
const [allDomains] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN);
2931

30-
// Find the last route the user had open in the Workspaces tab (workspace, domain, or list).
31-
// Priority: live nav state (root level) -> inside TabNavigator -> preserved state -> session storage.
32-
const routeState = useRootNavigationState((rootState) => {
33-
const topmostFullScreenRoute = rootState?.routes?.findLast((route) => isFullScreenName(route.name));
34-
if (!topmostFullScreenRoute) {
35-
return {};
36-
}
37-
38-
// Multiple TAB_NAVIGATOR instances can coexist in the root stack — when navigation from
39-
// inside an RHP targets a tab, linkTo PUSHes a fresh TabNavigator above the modal, and that
40-
// new instance's WORKSPACE_NAVIGATOR slot starts empty. Older instances kept alive by
41-
// ensureTabNavigatorRoutes still hold the previous workspace state, so flatten every
42-
// workspace route from every TabNavigator in stack order and take the most recent one.
43-
const lastWorkspaceRoute = (rootState?.routes ?? [])
44-
.filter((route) => route.name === NAVIGATORS.TAB_NAVIGATOR)
45-
.flatMap((tabNavigatorRoute) => {
46-
const workspaceNavigatorRoute = getTabState(tabNavigatorRoute)?.routes?.find((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR);
47-
const workspaceNavigatorState = workspaceNavigatorRoute?.state ?? (workspaceNavigatorRoute?.key ? getPreservedNavigatorState(workspaceNavigatorRoute.key) : undefined);
48-
return workspaceNavigatorState?.routes?.filter((route) => isWorkspaceNavigatorRouteName(route.name)) ?? [];
49-
})
50-
.at(-1);
32+
return () => {
33+
// Find the last route the user had open in the Workspaces tab (workspace, domain, or list).
34+
// Priority: live nav state (root level) -> inside TabNavigator -> preserved state -> session storage.
35+
const rootState = navigationRef.isReady() ? navigationRef.getRootState() : undefined;
36+
const routeState = (() => {
37+
const topmostFullScreenRoute = rootState?.routes?.findLast((route) => isFullScreenName(route.name));
38+
if (!topmostFullScreenRoute) {
39+
return {};
40+
}
5141

52-
if (lastWorkspaceRoute) {
53-
const tabState = lastWorkspaceRoute.state ?? (lastWorkspaceRoute.key ? getPreservedNavigatorState(lastWorkspaceRoute.key) : undefined);
54-
return {lastWorkspacesTabNavigatorRoute: lastWorkspaceRoute, workspacesTabState: tabState, topmostFullScreenRoute};
55-
}
42+
// Multiple TAB_NAVIGATOR instances can coexist in the root stack — when navigation from
43+
// inside an RHP targets a tab, linkTo PUSHes a fresh TabNavigator above the modal, and that
44+
// new instance's WORKSPACE_NAVIGATOR slot starts empty. Older instances kept alive by
45+
// ensureTabNavigatorRoutes still hold the previous workspace state, so flatten every
46+
// workspace route from every TabNavigator in stack order and take the most recent one.
47+
const lastWorkspaceRoute = (rootState?.routes ?? [])
48+
.filter((route) => route.name === NAVIGATORS.TAB_NAVIGATOR)
49+
.flatMap((tabNavigatorRoute) => {
50+
const workspaceNavigatorRoute = getTabState(tabNavigatorRoute)?.routes?.find((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR);
51+
const workspaceNavigatorState = workspaceNavigatorRoute?.state ?? (workspaceNavigatorRoute?.key ? getPreservedNavigatorState(workspaceNavigatorRoute.key) : undefined);
52+
return workspaceNavigatorState?.routes?.filter((route) => isWorkspaceNavigatorRouteName(route.name)) ?? [];
53+
})
54+
.at(-1);
5655

57-
// Fall back to session storage when no workspace route exists anywhere in the navigation tree.
58-
const sessionRoute = getWorkspacesTabStateFromSessionStorage()
59-
?.routes?.findLast((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR)
60-
?.state?.routes?.findLast((route) => isWorkspaceNavigatorRouteName(route.name));
61-
if (sessionRoute) {
62-
return {lastWorkspacesTabNavigatorRoute: sessionRoute, workspacesTabState: sessionRoute.state, topmostFullScreenRoute};
63-
}
56+
if (lastWorkspaceRoute) {
57+
const tabState = lastWorkspaceRoute.state ?? (lastWorkspaceRoute.key ? getPreservedNavigatorState(lastWorkspaceRoute.key) : undefined);
58+
return {lastWorkspacesTabNavigatorRoute: lastWorkspaceRoute, workspacesTabState: tabState, topmostFullScreenRoute};
59+
}
6460

65-
return {topmostFullScreenRoute};
66-
});
61+
// Fall back to session storage when no workspace route exists anywhere in the navigation tree.
62+
const sessionRoute = getWorkspacesTabStateFromSessionStorage()
63+
?.routes?.findLast((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR)
64+
?.state?.routes?.findLast((route) => isWorkspaceNavigatorRouteName(route.name));
65+
if (sessionRoute) {
66+
return {lastWorkspacesTabNavigatorRoute: sessionRoute, workspacesTabState: sessionRoute.state, topmostFullScreenRoute};
67+
}
6768

68-
const {lastWorkspacesTabNavigatorRoute, workspacesTabState, topmostFullScreenRoute} = routeState;
69+
return {topmostFullScreenRoute};
70+
})();
6971

70-
// If the last route was a specific workspace or domain, extract its ID from params
71-
const params = workspacesTabState?.routes?.at(0)?.params as
72-
| WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL]
73-
| DomainSplitNavigatorParamList[typeof SCREENS.DOMAIN.INITIAL];
74-
const paramsPolicyID = params && 'policyID' in params ? params.policyID : undefined;
75-
const paramsDomainAccountID = params && 'domainAccountID' in params ? params.domainAccountID : undefined;
72+
const {lastWorkspacesTabNavigatorRoute, workspacesTabState, topmostFullScreenRoute} = routeState;
7673

77-
// Fetch the policy/domain to verify it's still accessible (not deleted/hidden) before restoring
78-
const lastViewedPolicySelector = useCallback(
79-
(policies: OnyxCollection<Policy>) => {
80-
if (lastWorkspacesTabNavigatorRoute?.name !== NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR || !paramsPolicyID) {
81-
return undefined;
82-
}
83-
return policies?.[`${ONYXKEYS.COLLECTION.POLICY}${paramsPolicyID}`];
84-
},
85-
[lastWorkspacesTabNavigatorRoute?.name, paramsPolicyID],
86-
);
87-
const [lastViewedPolicy] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: lastViewedPolicySelector});
74+
// If the last route was a specific workspace or domain, extract its ID from params
75+
const params = workspacesTabState?.routes?.at(0)?.params as
76+
| WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL]
77+
| DomainSplitNavigatorParamList[typeof SCREENS.DOMAIN.INITIAL];
78+
const paramsPolicyID = params && 'policyID' in params ? params.policyID : undefined;
79+
const paramsDomainAccountID = params && 'domainAccountID' in params ? params.domainAccountID : undefined;
8880

89-
const lastViewedDomainSelector = useCallback(
90-
(domains: OnyxCollection<Domain>) => {
91-
if (lastWorkspacesTabNavigatorRoute?.name !== NAVIGATORS.DOMAIN_SPLIT_NAVIGATOR || !paramsDomainAccountID) {
92-
return undefined;
93-
}
94-
return domains?.[`${ONYXKEYS.COLLECTION.DOMAIN}${paramsDomainAccountID}`];
95-
},
96-
[lastWorkspacesTabNavigatorRoute?.name, paramsDomainAccountID],
97-
);
98-
const [lastViewedDomain] = useOnyx(ONYXKEYS.COLLECTION.DOMAIN, {selector: lastViewedDomainSelector});
81+
// Fetch the policy/domain to verify it's still accessible (not deleted/hidden) before restoring
82+
const lastViewedPolicy =
83+
lastWorkspacesTabNavigatorRoute?.name === NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR && paramsPolicyID ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${paramsPolicyID}`] : undefined;
84+
const lastViewedDomain =
85+
lastWorkspacesTabNavigatorRoute?.name === NAVIGATORS.DOMAIN_SPLIT_NAVIGATOR && paramsDomainAccountID
86+
? allDomains?.[`${ONYXKEYS.COLLECTION.DOMAIN}${paramsDomainAccountID}`]
87+
: undefined;
9988

100-
return useCallback(() => {
10189
navigateToWorkspacesPage({
10290
shouldUseNarrowLayout,
10391
currentUserLogin,
@@ -107,7 +95,7 @@ function useRestoreWorkspacesTabOnNavigate() {
10795
topmostFullScreenRoute,
10896
workspacesTabState,
10997
});
110-
}, [shouldUseNarrowLayout, currentUserLogin, lastViewedPolicy, lastViewedDomain, lastWorkspacesTabNavigatorRoute, topmostFullScreenRoute, workspacesTabState]);
98+
};
11199
}
112100

113101
export default useRestoreWorkspacesTabOnNavigate;

0 commit comments

Comments
 (0)