|
1 | | -import {useCallback} from 'react'; |
2 | | -import type {OnyxCollection} from 'react-native-onyx'; |
| 1 | +import type {NavigationState, PartialState} from '@react-navigation/native'; |
3 | 2 | import {getPreservedNavigatorState} from '@libs/Navigation/AppNavigator/createSplitNavigator/usePreserveNavigatorState'; |
4 | 3 | import {isFullScreenName, isWorkspaceNavigatorRouteName} from '@libs/Navigation/helpers/isNavigatorName'; |
5 | 4 | import {getWorkspacesTabStateFromSessionStorage} from '@libs/Navigation/helpers/lastVisitedTabPathUtils'; |
6 | 5 | import navigateToWorkspacesPage from '@libs/Navigation/helpers/navigateToWorkspacesPage'; |
7 | 6 | import {getTabState} from '@libs/Navigation/helpers/tabNavigatorUtils'; |
| 7 | +import navigationRef from '@libs/Navigation/navigationRef'; |
8 | 8 | import type {DomainSplitNavigatorParamList, WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; |
9 | 9 | import NAVIGATORS from '@src/NAVIGATORS'; |
10 | | -import ONYXKEYS from '@src/ONYXKEYS'; |
11 | 10 | import type SCREENS from '@src/SCREENS'; |
12 | | -import type {Domain, Policy} from '@src/types/onyx'; |
13 | | -import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; |
14 | | -import useOnyx from './useOnyx'; |
15 | 11 | import useResponsiveLayout from './useResponsiveLayout'; |
16 | | -import useRootNavigationState from './useRootNavigationState'; |
| 12 | + |
| 13 | +type WorkspaceRouteType = NavigationState['routes'][number] | NonNullable<PartialState<NavigationState>['routes']>[number]; |
| 14 | +type WorkspaceParams = WorkspaceSplitNavigatorParamList[typeof SCREENS.WORKSPACE.INITIAL] | DomainSplitNavigatorParamList[typeof SCREENS.DOMAIN.INITIAL]; |
| 15 | + |
| 16 | +/** |
| 17 | + * Walks the root nav state to find the last workspace/domain route the user had open. |
| 18 | + * Falls back to session storage when no live workspace route exists. |
| 19 | + * |
| 20 | + * Multiple TAB_NAVIGATOR instances can coexist in the root stack — when navigation from |
| 21 | + * inside an RHP targets a tab, linkTo PUSHes a fresh TabNavigator above the modal, and that |
| 22 | + * new instance's WORKSPACE_NAVIGATOR slot starts empty. Older instances kept alive by |
| 23 | + * ensureTabNavigatorRoutes still hold the previous workspace state, so flatten every |
| 24 | + * workspace route from every TabNavigator in stack order and take the most recent one. |
| 25 | + */ |
| 26 | +function findLastWorkspaceRoute(rootState: NavigationState | undefined): WorkspaceRouteType | undefined { |
| 27 | + const topmostFullScreenRoute = rootState?.routes?.findLast((route) => isFullScreenName(route.name)); |
| 28 | + if (!topmostFullScreenRoute) { |
| 29 | + return undefined; |
| 30 | + } |
| 31 | + |
| 32 | + const lastWorkspaceRoute = (rootState?.routes ?? []) |
| 33 | + .filter((route) => route.name === NAVIGATORS.TAB_NAVIGATOR) |
| 34 | + .flatMap((tabNavigatorRoute) => { |
| 35 | + const workspaceNavigatorRoute = getTabState(tabNavigatorRoute)?.routes?.find((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR); |
| 36 | + const workspaceNavigatorState = workspaceNavigatorRoute?.state ?? (workspaceNavigatorRoute?.key ? getPreservedNavigatorState(workspaceNavigatorRoute.key) : undefined); |
| 37 | + return workspaceNavigatorState?.routes?.filter((route) => isWorkspaceNavigatorRouteName(route.name)) ?? []; |
| 38 | + }) |
| 39 | + .at(-1); |
| 40 | + |
| 41 | + if (lastWorkspaceRoute) { |
| 42 | + return lastWorkspaceRoute; |
| 43 | + } |
| 44 | + |
| 45 | + return getWorkspacesTabStateFromSessionStorage() |
| 46 | + ?.routes?.findLast((route) => route.name === NAVIGATORS.WORKSPACE_NAVIGATOR) |
| 47 | + ?.state?.routes?.findLast((route) => isWorkspaceNavigatorRouteName(route.name)); |
| 48 | +} |
| 49 | + |
| 50 | +function getWorkspacesTabState(route: WorkspaceRouteType | undefined): NavigationState | PartialState<NavigationState> | undefined { |
| 51 | + if (!route) { |
| 52 | + return undefined; |
| 53 | + } |
| 54 | + return route.state ?? (route.key ? getPreservedNavigatorState(route.key) : undefined); |
| 55 | +} |
17 | 56 |
|
18 | 57 | /** |
19 | 58 | * The Workspaces tab can show three things: the workspaces list, a specific workspace page, |
20 | 59 | * or a specific domain page. When the user navigates away and comes back to the tab, |
21 | 60 | * this hook ensures they return to whichever of those they had open last — not always the list. |
22 | 61 | * |
23 | | - * It resolves the last visited route from navigation state, fetches the matching policy/domain |
24 | | - * from Onyx (to verify it's still accessible), and returns a callback that performs the navigation. |
| 62 | + * Resolves nav state and route IDs at click time inside the returned callback so the hook |
| 63 | + * has no reactive subscriptions to nav state — unrelated navigations (e.g. opening a report) |
| 64 | + * don't trigger re-renders. The destination workspace/domain page handles invalid IDs. |
25 | 65 | */ |
26 | 66 | function useRestoreWorkspacesTabOnNavigate() { |
27 | 67 | const {shouldUseNarrowLayout} = useResponsiveLayout(); |
28 | | - const {login: currentUserLogin} = useCurrentUserPersonalDetails(); |
29 | 68 |
|
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) => { |
| 69 | + return () => { |
| 70 | + const rootState = navigationRef.isReady() ? navigationRef.getRootState() : undefined; |
33 | 71 | 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); |
51 | | - |
52 | | - if (lastWorkspaceRoute) { |
53 | | - const tabState = lastWorkspaceRoute.state ?? (lastWorkspaceRoute.key ? getPreservedNavigatorState(lastWorkspaceRoute.key) : undefined); |
54 | | - return {lastWorkspacesTabNavigatorRoute: lastWorkspaceRoute, workspacesTabState: tabState, topmostFullScreenRoute}; |
55 | | - } |
56 | | - |
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 | | - } |
64 | | - |
65 | | - return {topmostFullScreenRoute}; |
66 | | - }); |
67 | | - |
68 | | - const {lastWorkspacesTabNavigatorRoute, workspacesTabState, topmostFullScreenRoute} = routeState; |
69 | | - |
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; |
76 | | - |
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}); |
| 72 | + const lastWorkspacesTabNavigatorRoute = findLastWorkspaceRoute(rootState); |
| 73 | + const workspacesTabState = getWorkspacesTabState(lastWorkspacesTabNavigatorRoute); |
88 | 74 |
|
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}); |
| 75 | + const params = workspacesTabState?.routes?.at(0)?.params as WorkspaceParams | undefined; |
| 76 | + const policyID = params && 'policyID' in params ? params.policyID : undefined; |
| 77 | + const domainAccountID = params && 'domainAccountID' in params ? params.domainAccountID : undefined; |
99 | 78 |
|
100 | | - return useCallback(() => { |
101 | 79 | navigateToWorkspacesPage({ |
102 | 80 | shouldUseNarrowLayout, |
103 | | - currentUserLogin, |
104 | | - policy: lastViewedPolicy, |
105 | | - domain: lastViewedDomain, |
| 81 | + policyID, |
| 82 | + domainAccountID, |
106 | 83 | lastWorkspacesTabNavigatorRoute, |
107 | 84 | topmostFullScreenRoute, |
108 | 85 | workspacesTabState, |
109 | 86 | }); |
110 | | - }, [shouldUseNarrowLayout, currentUserLogin, lastViewedPolicy, lastViewedDomain, lastWorkspacesTabNavigatorRoute, topmostFullScreenRoute, workspacesTabState]); |
| 87 | + }; |
111 | 88 | } |
112 | 89 |
|
113 | 90 | export default useRestoreWorkspacesTabOnNavigate; |
0 commit comments