1"use strict"; 2Object.defineProperty(exports, "__esModule", { value: true }); 3exports.useInitializeExpoRouter = exports.useStoreRouteInfo = exports.useStoreRootState = exports.useExpoRouter = exports.store = exports.RouterStore = void 0; 4const native_1 = require("@react-navigation/native"); 5const react_1 = require("react"); 6const routing_1 = require("./routing"); 7const sort_routes_1 = require("./sort-routes"); 8const LocationProvider_1 = require("../LocationProvider"); 9const getPathFromState_1 = require("../fork/getPathFromState"); 10const getLinkingConfig_1 = require("../getLinkingConfig"); 11const getRoutes_1 = require("../getRoutes"); 12const useScreens_1 = require("../useScreens"); 13const Splash_1 = require("../views/Splash"); 14/** 15 * This is the global state for the router. It is used to keep track of the current route, and to provide a way to navigate to other routes. 16 * 17 * There should only be one instance of this class and be initialized via `useInitializeExpoRouter` 18 */ 19class RouterStore { 20 routeNode; 21 rootComponent; 22 linking; 23 hasAttemptedToHideSplash = false; 24 initialState; 25 rootState; 26 nextState; 27 routeInfo; 28 navigationRef; 29 navigationRefSubscription; 30 rootStateSubscribers = new Set(); 31 storeSubscribers = new Set(); 32 linkTo = routing_1.linkTo.bind(this); 33 getSortedRoutes = sort_routes_1.getSortedRoutes.bind(this); 34 goBack = routing_1.goBack.bind(this); 35 canGoBack = routing_1.canGoBack.bind(this); 36 push = routing_1.push.bind(this); 37 replace = routing_1.replace.bind(this); 38 setParams = routing_1.setParams.bind(this); 39 initialize(context, navigationRef, initialLocation) { 40 // Clean up any previous state 41 this.initialState = undefined; 42 this.rootState = undefined; 43 this.nextState = undefined; 44 this.routeInfo = undefined; 45 this.linking = undefined; 46 this.navigationRefSubscription?.(); 47 this.rootStateSubscribers.clear(); 48 this.storeSubscribers.clear(); 49 this.routeNode = (0, getRoutes_1.getRoutes)(context); 50 this.rootComponent = this.routeNode ? (0, useScreens_1.getQualifiedRouteComponent)(this.routeNode) : react_1.Fragment; 51 // Only error in production, in development we will show the onboarding screen 52 if (!this.routeNode && process.env.NODE_ENV === 'production') { 53 throw new Error('No routes found'); 54 } 55 this.navigationRef = navigationRef; 56 if (this.routeNode) { 57 this.linking = (0, getLinkingConfig_1.getLinkingConfig)(this.routeNode); 58 if (initialLocation) { 59 this.linking.getInitialURL = () => initialLocation.toString(); 60 this.initialState = this.linking.getStateFromPath?.(initialLocation.pathname + initialLocation.search, this.linking.config); 61 } 62 } 63 // There is no routeNode, so we will be showing the onboarding screen 64 // In the meantime, just mock the routeInfo 65 if (this.initialState) { 66 this.rootState = this.initialState; 67 this.routeInfo = this.getRouteInfo(this.initialState); 68 } 69 else { 70 this.routeInfo = { 71 unstable_globalHref: '', 72 pathname: '', 73 params: {}, 74 segments: [], 75 }; 76 } 77 /** 78 * Counter intuitively - this fires AFTER both React Navigations state change and the subsequent paint. 79 * This poses a couple of issues for Expo Router, 80 * - Ensuring hooks (e.g. useSearchParams()) have data in the initial render 81 * - Reacting to state changes after a navigation event 82 * 83 * This is why the initial render renders a Fragment and we wait until `onReady()` is called 84 * Additionally, some hooks compare the state from both the store and the navigationRef. If the store it stale, 85 * that hooks will manually update the store. 86 * 87 */ 88 this.navigationRefSubscription = navigationRef.addListener('state', (data) => { 89 const state = data.data.state; 90 if (!this.hasAttemptedToHideSplash) { 91 this.hasAttemptedToHideSplash = true; 92 // NOTE(EvanBacon): `navigationRef.isReady` is sometimes not true when state is called initially. 93 requestAnimationFrame(() => (0, Splash_1._internal_maybeHideAsync)()); 94 } 95 let shouldUpdateSubscribers = this.nextState === state; 96 this.nextState = undefined; 97 // This can sometimes be undefined when an error is thrown in the Root Layout Route. 98 // Additionally that state may already equal the rootState if it was updated within a hook 99 if (state && state !== this.rootState) { 100 exports.store.updateState(state, undefined); 101 shouldUpdateSubscribers = true; 102 } 103 // If the state has changed, or was changed inside a hook we need to update the subscribers 104 if (shouldUpdateSubscribers) { 105 for (const subscriber of this.rootStateSubscribers) { 106 subscriber(); 107 } 108 } 109 }); 110 for (const subscriber of this.storeSubscribers) { 111 subscriber(); 112 } 113 } 114 updateState(state, nextState = state) { 115 exports.store.rootState = state; 116 exports.store.nextState = nextState; 117 const nextRouteInfo = exports.store.getRouteInfo(state); 118 if (!(0, getPathFromState_1.deepEqual)(this.routeInfo, nextRouteInfo)) { 119 exports.store.routeInfo = nextRouteInfo; 120 } 121 } 122 getRouteInfo(state) { 123 return (0, LocationProvider_1.getRouteInfoFromState)((state, asPath) => { 124 return (0, getPathFromState_1.getPathDataFromState)(state, { 125 screens: [], 126 ...this.linking?.config, 127 preserveDynamicRoutes: asPath, 128 preserveGroups: asPath, 129 }); 130 }, state); 131 } 132 // This is only used in development, to show the onboarding screen 133 // In production we should have errored during the initialization 134 shouldShowTutorial() { 135 return !this.routeNode && process.env.NODE_ENV === 'development'; 136 } 137 /** Make sure these are arrow functions so `this` is correctly bound */ 138 subscribeToRootState = (subscriber) => { 139 this.rootStateSubscribers.add(subscriber); 140 return () => this.rootStateSubscribers.delete(subscriber); 141 }; 142 subscribeToStore = (subscriber) => { 143 this.storeSubscribers.add(subscriber); 144 return () => this.storeSubscribers.delete(subscriber); 145 }; 146 snapshot = () => { 147 return this; 148 }; 149 rootStateSnapshot = () => { 150 return this.rootState; 151 }; 152 routeInfoSnapshot = () => { 153 return this.routeInfo; 154 }; 155} 156exports.RouterStore = RouterStore; 157exports.store = new RouterStore(); 158function useExpoRouter() { 159 return (0, react_1.useSyncExternalStore)(exports.store.subscribeToStore, exports.store.snapshot, exports.store.snapshot); 160} 161exports.useExpoRouter = useExpoRouter; 162function syncStoreRootState() { 163 if (exports.store.navigationRef.isReady()) { 164 const currentState = exports.store.navigationRef.getRootState(); 165 if (exports.store.rootState !== currentState) { 166 exports.store.updateState(currentState); 167 } 168 } 169} 170function useStoreRootState() { 171 syncStoreRootState(); 172 return (0, react_1.useSyncExternalStore)(exports.store.subscribeToRootState, exports.store.rootStateSnapshot, exports.store.rootStateSnapshot); 173} 174exports.useStoreRootState = useStoreRootState; 175function useStoreRouteInfo() { 176 syncStoreRootState(); 177 return (0, react_1.useSyncExternalStore)(exports.store.subscribeToRootState, exports.store.routeInfoSnapshot, exports.store.routeInfoSnapshot); 178} 179exports.useStoreRouteInfo = useStoreRouteInfo; 180function useInitializeExpoRouter(context, initialLocation) { 181 const navigationRef = (0, native_1.useNavigationContainerRef)(); 182 (0, react_1.useMemo)(() => exports.store.initialize(context, navigationRef, initialLocation), [context, initialLocation]); 183 useExpoRouter(); 184 return exports.store; 185} 186exports.useInitializeExpoRouter = useInitializeExpoRouter; 187//# sourceMappingURL=router-store.js.map