expo-router 1.7.7 → 2.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/_entry.tsx CHANGED
@@ -6,13 +6,14 @@ import React from "react";
6
6
 
7
7
  import { ctx } from "./_ctx";
8
8
  import { ExpoRoot } from "./src";
9
+ import { ExpoRootProps } from "./src/ExpoRoot";
9
10
  import { getNavigationConfig } from "./src/getLinkingConfig";
10
11
  import { getRoutes } from "./src/getRoutes";
11
12
  import { loadStaticParamsAsync } from "./src/loadStaticParamsAsync";
12
13
 
13
14
  // Must be exported or Fast Refresh won't update the context >:[
14
- export default function ExpoRouterRoot({ location }: { location: URL }) {
15
- return <ExpoRoot context={ctx} location={location} />;
15
+ export default function ExpoRouterRoot(props: Omit<ExpoRootProps, "context">) {
16
+ return <ExpoRoot context={ctx} {...props} />;
16
17
  }
17
18
 
18
19
  /** Get the linking manifest from a Node.js process. */
package/babel.js CHANGED
@@ -59,7 +59,7 @@ function getExpoRouterImportMode(projectRoot, platform) {
59
59
  }
60
60
 
61
61
  // NOTE: This is a temporary workaround for static rendering on web.
62
- if (platform === "web" && process.env.EXPO_USE_STATIC) {
62
+ if (platform === "web" && (exp.web || {}).output === "static") {
63
63
  mode = "sync";
64
64
  }
65
65
 
@@ -1,8 +1,11 @@
1
- /// <reference types="react" />
1
+ import { FunctionComponent, ReactNode } from "react";
2
2
  import { RequireContext } from "./types";
3
3
  export type ExpoRootProps = {
4
4
  context: RequireContext;
5
5
  location?: URL;
6
+ wrapper?: FunctionComponent<{
7
+ children: ReactNode;
8
+ }>;
6
9
  };
7
- export declare function ExpoRoot({ context, location }: ExpoRootProps): JSX.Element;
10
+ export declare function ExpoRoot({ wrapper: ParentWrapper, ...props }: ExpoRootProps): JSX.Element;
8
11
  //# sourceMappingURL=ExpoRoot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ExpoRoot.d.ts","sourceRoot":"","sources":["../src/ExpoRoot.tsx"],"names":[],"mappings":";AAOA,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGzC,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB,CAAC;AA4BF,wBAAgB,QAAQ,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,aAAa,eAa5D"}
1
+ {"version":3,"file":"ExpoRoot.d.ts","sourceRoot":"","sources":["../src/ExpoRoot.tsx"],"names":[],"mappings":"AACA,OAAc,EAAE,iBAAiB,EAAE,SAAS,EAAY,MAAM,OAAO,CAAC;AAMtE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGzC,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,OAAO,CAAC,EAAE,iBAAiB,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;CACtD,CAAC;AAgCF,wBAAgB,QAAQ,CAAC,EACvB,OAAO,EAAE,aAAwB,EACjC,GAAG,KAAK,EACT,EAAE,aAAa,eAyBf"}
@@ -1 +1 @@
1
- {"version":3,"file":"router-store.d.ts","sourceRoot":"","sources":["../../src/global-state/router-store.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iCAAiC,EAGlC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAiC,aAAa,EAAY,MAAM,OAAO,CAAC;AAE/E,OAAO,EAAE,SAAS,EAAyB,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAoB,MAAM,qBAAqB,CAAC;AAE3E,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAM1C;;;;GAIG;AACH,qBAAa,WAAW;IACtB,SAAS,EAAG,SAAS,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAG,aAAa,CAAC;IAC9B,OAAO,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACxC,OAAO,EAAE,OAAO,CAAS;IAEzB,YAAY,EAAE,WAAW,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,WAAW,GAAG,SAAS,CAAC;IACnC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAElC,aAAa,EAAG,iCAAiC,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IACjF,yBAAyB,EAAG,MAAM,IAAI,CAAC;IAEvC,oBAAoB,YAAiB,IAAI,EAAI;IAC7C,gBAAgB,YAAiB,IAAI,EAAI;IAEzC,MAAM,qDAAqB;IAC3B,eAAe,oBAA8B;IAC7C,MAAM,aAAqB;IAC3B,IAAI,6CAAmB;IACvB,OAAO,6CAAsB;IAC7B,SAAS,gEAAwB;IAEjC,UAAU,CACR,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,iCAAiC,CAAC,eAAe,CAAC,aAAa,CAAC,EAC/E,eAAe,CAAC,EAAE,GAAG;IA2EvB,YAAY,CAAC,KAAK,EAAE,WAAW;IAgB/B,kBAAkB;IAIlB,uEAAuE;IACvE,OAAO,aAKL;IACF,oBAAoB,eAAgB,MAAM,IAAI,mBAG5C;IACF,gBAAgB,eAAgB,MAAM,IAAI,mBAGxC;IACF,QAAQ,aAEN;IACF,iBAAiB,oBAEf;IACF,iBAAiB,kBAEf;CACH;AAED,eAAO,MAAM,KAAK,aAAoB,CAAC;AAEvC,wBAAgB,aAAa,gBAM5B;AAED,wBAAgB,iBAAiB,gBAMhC;AAED,wBAAgB,iBAAiB,cAMhC;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,cAAc,EACvB,eAAe,EAAE,GAAG,GAAG,SAAS,eASjC"}
1
+ {"version":3,"file":"router-store.d.ts","sourceRoot":"","sources":["../../src/global-state/router-store.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,iCAAiC,EAGlC,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAiC,aAAa,EAAY,MAAM,OAAO,CAAC;AAE/E,OAAO,EAAE,SAAS,EAAyB,MAAM,qBAAqB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAoB,MAAM,qBAAqB,CAAC;AAE3E,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAM1C;;;;GAIG;AACH,qBAAa,WAAW;IACtB,SAAS,EAAG,SAAS,GAAG,IAAI,CAAC;IAC7B,aAAa,EAAG,aAAa,CAAC;IAC9B,OAAO,EAAE,kBAAkB,GAAG,SAAS,CAAC;IACxC,OAAO,EAAE,OAAO,CAAS;IAEzB,YAAY,EAAE,WAAW,GAAG,SAAS,CAAC;IACtC,SAAS,EAAE,WAAW,GAAG,SAAS,CAAC;IACnC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAElC,aAAa,EAAG,iCAAiC,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;IACjF,yBAAyB,EAAG,MAAM,IAAI,CAAC;IAEvC,oBAAoB,YAAiB,IAAI,EAAI;IAC7C,gBAAgB,YAAiB,IAAI,EAAI;IAEzC,MAAM,qDAAqB;IAC3B,eAAe,oBAA8B;IAC7C,MAAM,aAAqB;IAC3B,IAAI,6CAAmB;IACvB,OAAO,6CAAsB;IAC7B,SAAS,gEAAwB;IAEjC,UAAU,CACR,OAAO,EAAE,cAAc,EACvB,aAAa,EAAE,iCAAiC,CAAC,eAAe,CAAC,aAAa,CAAC,EAC/E,eAAe,CAAC,EAAE,GAAG;IA6EvB,YAAY,CAAC,KAAK,EAAE,WAAW;IAgB/B,kBAAkB;IAIlB,uEAAuE;IACvE,OAAO,aAKL;IACF,oBAAoB,eAAgB,MAAM,IAAI,mBAG5C;IACF,gBAAgB,eAAgB,MAAM,IAAI,mBAGxC;IACF,QAAQ,aAEN;IACF,iBAAiB,oBAEf;IACF,iBAAiB,kBAEf;CACH;AAED,eAAO,MAAM,KAAK,aAAoB,CAAC;AAEvC,wBAAgB,aAAa,gBAM5B;AAED,wBAAgB,iBAAiB,gBAMhC;AAED,wBAAgB,iBAAiB,cAMhC;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,cAAc,EACvB,eAAe,EAAE,GAAG,GAAG,SAAS,eASjC"}
@@ -1,9 +1,9 @@
1
1
  /// <reference types="react" />
2
2
  import requireContext from "./require-context-ponyfill";
3
3
  export type ReactComponent = () => React.ReactElement<any, any> | null;
4
- export type FileStub = {
4
+ export type FileStub = (Record<string, unknown> & {
5
5
  default: ReactComponent;
6
- } | ReactComponent;
6
+ }) | ReactComponent;
7
7
  export { requireContext };
8
8
  export declare function inMemoryContext(context: Record<string, FileStub>): ((id: string) => ReactComponent | {
9
9
  default: FileStub;
@@ -1 +1 @@
1
- {"version":3,"file":"context-stubs.d.ts","sourceRoot":"","sources":["../../src/testing-library/context-stubs.ts"],"names":[],"mappings":";AAEA,OAAO,cAAc,MAAM,4BAA4B,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;AACvE,MAAM,MAAM,QAAQ,GAAG;IAAE,OAAO,EAAE,cAAc,CAAA;CAAE,GAAG,cAAc,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,SAE/C,MAAM;;;;mBAQH,MAAM;;EAI1B;AAED,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,SAKnB,MAAM;;mBAUH,MAAM;;EAI1B"}
1
+ {"version":3,"file":"context-stubs.d.ts","sourceRoot":"","sources":["../../src/testing-library/context-stubs.ts"],"names":[],"mappings":";AAEA,OAAO,cAAc,MAAM,4BAA4B,CAAC;AAExD,MAAM,MAAM,cAAc,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;AACvE,MAAM,MAAM,QAAQ,GAChB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,OAAO,EAAE,cAAc,CAAA;CAAE,CAAC,GACvD,cAAc,CAAC;AAEnB,OAAO,EAAE,cAAc,EAAE,CAAC;AAE1B,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,SAE/C,MAAM;;;;mBAQH,MAAM;;EAI1B;AAED,wBAAgB,2BAA2B,CACzC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,SAKnB,MAAM;;mBAUH,MAAM;;EAI1B"}
@@ -7,7 +7,8 @@ type RenderRouterOptions = Parameters<typeof render>[1] & {
7
7
  };
8
8
  type Result = ReturnType<typeof render> & {
9
9
  getPathname(): string;
10
- getSearchParams(): URLSearchParams;
10
+ getSegments(): string[];
11
+ getSearchParams(): Record<string, string | string[]>;
11
12
  };
12
13
  export declare function renderRouter(context?: string, options?: RenderRouterOptions): Result;
13
14
  export declare function renderRouter(context: Record<string, FileStub>, options?: RenderRouterOptions): Result;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing-library/index.tsx"],"names":[],"mappings":"AACA,OAAO,UAAU,CAAC;AAGlB,OAAO,EAAE,MAAM,EAAgB,MAAM,+BAA+B,CAAC;AAQrE,OAAO,EACL,QAAQ,EAIT,MAAM,iBAAiB,CAAC;AAIzB,cAAc,+BAA+B,CAAC;AAE9C,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;IACxD,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB,CAAC;AAEF,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,GAAG;IACxC,WAAW,IAAI,MAAM,CAAC;IACtB,eAAe,IAAI,eAAe,CAAC;CACpC,CAAC;AAQF,wBAAgB,YAAY,CAC1B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAAC;AACV,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EACjC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAAC;AACV,wBAAgB,YAAY,CAC1B,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAAE,EAChE,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/testing-library/index.tsx"],"names":[],"mappings":"AACA,OAAO,UAAU,CAAC;AAElB,OAAO,EAAE,MAAM,EAAgB,MAAM,+BAA+B,CAAC;AAQrE,OAAO,EACL,QAAQ,EAIT,MAAM,iBAAiB,CAAC;AAIzB,cAAc,+BAA+B,CAAC;AAE9C,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG;IACxD,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB,CAAC;AAEF,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,MAAM,CAAC,GAAG;IACxC,WAAW,IAAI,MAAM,CAAC;IACtB,WAAW,IAAI,MAAM,EAAE,CAAC;IACxB,eAAe,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;CACtD,CAAC;AAQF,wBAAgB,YAAY,CAC1B,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAAC;AACV,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,EACjC,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAAC;AACV,wBAAgB,YAAY,CAC1B,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CAAE,EAChE,OAAO,CAAC,EAAE,mBAAmB,GAC5B,MAAM,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"Unmatched.d.ts","sourceRoot":"","sources":["../../src/views/Unmatched.tsx"],"names":[],"mappings":";AAWA,2CAA2C;AAC3C,wBAAgB,SAAS,gBAkDxB"}
1
+ {"version":3,"file":"Unmatched.d.ts","sourceRoot":"","sources":["../../src/views/Unmatched.tsx"],"names":[],"mappings":";AAwBA,2CAA2C;AAC3C,wBAAgB,SAAS,gBAoDxB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-router",
3
- "version": "1.7.7",
3
+ "version": "2.0.0-rc.2",
4
4
  "main": "src/index.tsx",
5
5
  "types": "build/index.d.ts",
6
6
  "files": [
@@ -104,12 +104,12 @@
104
104
  },
105
105
  "dependencies": {
106
106
  "@bacons/react-views": "^1.1.3",
107
- "@expo/metro-runtime": "2.1.6",
107
+ "@expo/metro-runtime": "2.1.8",
108
108
  "@radix-ui/react-slot": "1.0.1",
109
109
  "@react-navigation/bottom-tabs": "~6.5.7",
110
110
  "@react-navigation/native": "~6.1.6",
111
111
  "@react-navigation/native-stack": "~6.9.12",
112
- "expo-head": "0.0.8",
112
+ "expo-head": "0.0.10",
113
113
  "expo-splash-screen": "*",
114
114
  "query-string": "7.1.3",
115
115
  "react-helmet-async": "^1.3.0",
@@ -1,7 +1,7 @@
1
1
  import { ConfigPlugin } from "expo/config-plugins";
2
2
  declare const withRouter: ConfigPlugin<{
3
3
  /** Production origin URL where assets in the public folder are hosted. The fetch function is polyfilled to support relative requests from this origin in production, development origin is inferred using the Expo CLI development server. */
4
- origin: string;
4
+ origin?: string;
5
5
  /** A more specific origin URL used in the `expo-router/head` module for iOS handoff. Defaults to `origin`. */
6
6
  headOrigin?: string;
7
7
  /** Changes the routes directory from `app` to another value. Defaults to `app`. Avoid using this property. */
@@ -24,6 +24,7 @@ const withRouter = (config, props) => {
24
24
  extra: {
25
25
  ...config.extra,
26
26
  router: {
27
+ origin: false,
27
28
  ...config.extra?.router,
28
29
  ...props,
29
30
  },
@@ -67,7 +67,6 @@
67
67
  ]
68
68
  }
69
69
  },
70
- "required": ["origin"],
71
70
  "additionalProperties": false
72
71
  }
73
72
  }
@@ -21,7 +21,7 @@ const withExpoHeadIos: ConfigPlugin = (config) => {
21
21
 
22
22
  const withRouter: ConfigPlugin<{
23
23
  /** Production origin URL where assets in the public folder are hosted. The fetch function is polyfilled to support relative requests from this origin in production, development origin is inferred using the Expo CLI development server. */
24
- origin: string;
24
+ origin?: string;
25
25
  /** A more specific origin URL used in the `expo-router/head` module for iOS handoff. Defaults to `origin`. */
26
26
  headOrigin?: string;
27
27
  /** Changes the routes directory from `app` to another value. Defaults to `app`. Avoid using this property. */
@@ -40,6 +40,7 @@ const withRouter: ConfigPlugin<{
40
40
  extra: {
41
41
  ...config.extra,
42
42
  router: {
43
+ origin: false,
43
44
  ...config.extra?.router,
44
45
  ...props,
45
46
  },
package/src/ExpoRoot.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import { StatusBar } from "expo-status-bar";
2
- import React from "react";
2
+ import React, { FunctionComponent, ReactNode, Fragment } from "react";
3
3
  import { Platform } from "react-native";
4
4
  import { SafeAreaProvider } from "react-native-safe-area-context";
5
5
 
@@ -11,6 +11,7 @@ import { SplashScreen } from "./views/Splash";
11
11
  export type ExpoRootProps = {
12
12
  context: RequireContext;
13
13
  location?: URL;
14
+ wrapper?: FunctionComponent<{ children: ReactNode }>;
14
15
  };
15
16
 
16
17
  function getGestureHandlerRootView() {
@@ -18,6 +19,10 @@ function getGestureHandlerRootView() {
18
19
  const { GestureHandlerRootView } =
19
20
  require("react-native-gesture-handler") as typeof import("react-native-gesture-handler");
20
21
 
22
+ if (!GestureHandlerRootView) {
23
+ return React.Fragment;
24
+ }
25
+
21
26
  // eslint-disable-next-line no-inner-declarations
22
27
  function GestureHandler(props: any) {
23
28
  return <GestureHandlerRootView style={{ flex: 1 }} {...props} />;
@@ -39,19 +44,34 @@ const INITIAL_METRICS = {
39
44
  insets: { top: 0, left: 0, right: 0, bottom: 0 },
40
45
  };
41
46
 
42
- export function ExpoRoot({ context, location }: ExpoRootProps) {
43
- return (
44
- <GestureHandlerRootView>
45
- <SafeAreaProvider
46
- // SSR support
47
- initialMetrics={INITIAL_METRICS}
48
- >
49
- <ContextNavigator context={context} location={location} />
50
- {/* Users can override this by adding another StatusBar element anywhere higher in the component tree. */}
51
- <StatusBar style="auto" />
52
- </SafeAreaProvider>
53
- </GestureHandlerRootView>
54
- );
47
+ export function ExpoRoot({
48
+ wrapper: ParentWrapper = Fragment,
49
+ ...props
50
+ }: ExpoRootProps) {
51
+ /*
52
+ * Due to static rendering we need to wrap these top level views in second wrapper
53
+ * View's like <GestureHandlerRootView /> generate a <div> so if the parent wrapper
54
+ * is a HTML document, we need to ensure its inside the <body>
55
+ */
56
+ const wrapper: ExpoRootProps["wrapper"] = ({ children }) => {
57
+ return (
58
+ <ParentWrapper>
59
+ <GestureHandlerRootView>
60
+ <SafeAreaProvider
61
+ // SSR support
62
+ initialMetrics={INITIAL_METRICS}
63
+ >
64
+ {children}
65
+
66
+ {/* Users can override this by adding another StatusBar element anywhere higher in the component tree. */}
67
+ <StatusBar style="auto" />
68
+ </SafeAreaProvider>
69
+ </GestureHandlerRootView>
70
+ </ParentWrapper>
71
+ );
72
+ };
73
+
74
+ return <ContextNavigator {...props} wrapper={wrapper} />;
55
75
  }
56
76
 
57
77
  const initialUrl =
@@ -62,6 +82,7 @@ const initialUrl =
62
82
  function ContextNavigator({
63
83
  context,
64
84
  location: initialLocation = initialUrl,
85
+ wrapper: WrapperComponent = Fragment,
65
86
  }: ExpoRootProps) {
66
87
  const store = useInitializeExpoRouter(context, initialLocation);
67
88
 
@@ -83,9 +104,10 @@ function ContextNavigator({
83
104
  ref={store.navigationRef}
84
105
  initialState={store.initialState}
85
106
  linking={store.linking}
86
- onReady={store.onReady}
87
107
  >
88
- <Component />
108
+ <WrapperComponent>
109
+ <Component />
110
+ </WrapperComponent>
89
111
  </UpstreamNavigationContainer>
90
112
  );
91
113
  }
@@ -61,6 +61,7 @@ export class RouterStore {
61
61
  this.storeSubscribers.clear();
62
62
 
63
63
  this.routeNode = getRoutes(context);
64
+
64
65
  this.rootComponent = this.routeNode
65
66
  ? getQualifiedRouteComponent(this.routeNode)
66
67
  : Fragment;
@@ -76,6 +77,7 @@ export class RouterStore {
76
77
  this.linking = getLinkingConfig(this.routeNode!);
77
78
 
78
79
  if (initialLocation) {
80
+ this.linking.getInitialURL = () => initialLocation.toString();
79
81
  this.initialState = this.linking.getStateFromPath?.(
80
82
  initialLocation.pathname + initialLocation.search,
81
83
  this.linking.config
@@ -41,28 +41,28 @@ export function getStaticContent(location: URL): string {
41
41
 
42
42
  const Root = getRootComponent();
43
43
 
44
- const out = React.createElement(Root, {
45
- // TODO: Use RNW view after they fix hydration for React 18
46
- // https://github.com/necolas/react-native-web/blob/e8098fd029102d7801c32c1ede792bce01808c00/packages/react-native-web/src/exports/render/index.js#L10
47
- // Otherwise this wraps the app with two extra divs
48
- children:
49
- // Inject the root tag using createElement to prevent any transforms like the ones in `@expo/html-elements`.
50
- React.createElement(
51
- "div",
52
- {
53
- id: "root",
54
- },
55
- <App location={location} />
56
- ),
57
- });
58
-
59
44
  // This MUST be run before `ReactDOMServer.renderToString` to prevent
60
45
  // "Warning: Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported."
61
46
  resetReactNavigationContexts();
62
47
 
63
48
  const html = ReactDOMServer.renderToString(
64
49
  <Head.Provider context={headContext}>
65
- <ServerContainer ref={ref}>{out}</ServerContainer>
50
+ <ServerContainer ref={ref}>
51
+ <App
52
+ location={location}
53
+ wrapper={({ children }) => {
54
+ return React.createElement(Root, {
55
+ children: React.createElement(
56
+ "div",
57
+ {
58
+ id: "root",
59
+ },
60
+ children
61
+ ),
62
+ });
63
+ }}
64
+ />
65
+ </ServerContainer>
66
66
  </Head.Provider>
67
67
  );
68
68
 
@@ -3,7 +3,9 @@ import path from "path";
3
3
  import requireContext from "./require-context-ponyfill";
4
4
 
5
5
  export type ReactComponent = () => React.ReactElement<any, any> | null;
6
- export type FileStub = { default: ReactComponent } | ReactComponent;
6
+ export type FileStub =
7
+ | (Record<string, unknown> & { default: ReactComponent })
8
+ | ReactComponent;
7
9
 
8
10
  export { requireContext };
9
11
 
@@ -6,10 +6,10 @@ expect.extend({
6
6
  toHavePathname(screen, expected) {
7
7
  return matchers.toEqual(screen.getPathname(), expected);
8
8
  },
9
+ toHaveSegments(screen, expected) {
10
+ return matchers.toEqual(screen.getSegments(), expected);
11
+ },
9
12
  toHaveSearchParams(screen, expected) {
10
- return matchers.toEqual(
11
- Object.fromEntries(screen.getSearchParams().entries()),
12
- expected
13
- );
13
+ return matchers.toEqual(screen.getSearchParams(), expected);
14
14
  },
15
15
  });
@@ -1,14 +1,13 @@
1
- /// <reference types="../../types/jest.d.ts" />
1
+ /// <reference types="../../types/jest" />
2
2
  import "./expect";
3
3
 
4
- import { BaseNavigationContainer } from "@react-navigation/core";
5
4
  import { render, RenderResult } from "@testing-library/react-native";
6
- import { findAll } from "@testing-library/react-native/build/helpers/findAll";
7
5
  import path from "path";
8
6
  import React from "react";
9
7
 
10
8
  import { ExpoRoot } from "../ExpoRoot";
11
9
  import { stateCache } from "../getLinkingConfig";
10
+ import { store } from "../global-state/router-store";
12
11
  import { RequireContext } from "../types";
13
12
  import {
14
13
  FileStub,
@@ -27,7 +26,8 @@ type RenderRouterOptions = Parameters<typeof render>[1] & {
27
26
 
28
27
  type Result = ReturnType<typeof render> & {
29
28
  getPathname(): string;
30
- getSearchParams(): URLSearchParams;
29
+ getSegments(): string[];
30
+ getSearchParams(): Record<string, string | string[]>;
31
31
  };
32
32
 
33
33
  function isOverrideContext(
@@ -80,7 +80,7 @@ export function renderRouter(
80
80
  let location: URL | undefined;
81
81
 
82
82
  if (typeof initialUrl === "string") {
83
- location = new URL(initialUrl, "test://test");
83
+ location = new URL(initialUrl, "test://");
84
84
  } else if (initialUrl instanceof URL) {
85
85
  location = initialUrl;
86
86
  }
@@ -91,29 +91,13 @@ export function renderRouter(
91
91
 
92
92
  return Object.assign(result, {
93
93
  getPathname(this: RenderResult): string {
94
- const containers = findAll(this.root, (node) => {
95
- return node.type === BaseNavigationContainer;
96
- });
97
-
98
- return (
99
- "/" +
100
- containers
101
- .flatMap((route) => {
102
- return route.props.initialState.routes.map((r: any) => r.name);
103
- })
104
- .join("/")
105
- );
94
+ return store.routeInfoSnapshot().pathname;
106
95
  },
107
- getSearchParams(this: RenderResult): URLSearchParams {
108
- const containers = findAll(this.root, (node) => {
109
- return node.type === BaseNavigationContainer;
110
- });
111
-
112
- const params = containers.reduce<Record<string, string>>((acc, route) => {
113
- return { ...acc, ...route.props.initialState.routes[0].params };
114
- }, {});
115
-
116
- return new URLSearchParams(params);
96
+ getSegments(this: RenderResult): string[] {
97
+ return store.routeInfoSnapshot().segments;
98
+ },
99
+ getSearchParams(this: RenderResult): Record<string, string | string[]> {
100
+ return store.routeInfoSnapshot().params;
117
101
  },
118
102
  });
119
103
  }
@@ -80,7 +80,7 @@ const styles = StyleSheet.create({
80
80
  borderWidth: 1,
81
81
  borderColor: "rgba(255,255,255,0.2)",
82
82
  flexDirection: "row",
83
- position: "absolute",
83
+ position: "fixed",
84
84
  bottom: 8,
85
85
  left: 8,
86
86
  paddingVertical: 8,
@@ -9,6 +9,19 @@ import { useNavigation } from "../useNavigation";
9
9
  const useLayoutEffect =
10
10
  typeof window !== "undefined" ? React.useLayoutEffect : function () {};
11
11
 
12
+ function NoSSR({ children }: { children: React.ReactNode }) {
13
+ const [render, setRender] = React.useState(false);
14
+ React.useEffect(() => {
15
+ setRender(true);
16
+ }, []);
17
+
18
+ if (!render) {
19
+ return null;
20
+ }
21
+
22
+ return <>{children}</>;
23
+ }
24
+
12
25
  /** Default screen for unmatched routes. */
13
26
  export function Unmatched() {
14
27
  const router = useRouter();
@@ -51,9 +64,11 @@ export function Unmatched() {
51
64
  </Text>
52
65
  </Text>
53
66
 
54
- <Link href={pathname} replace style={styles.link}>
55
- {url}
56
- </Link>
67
+ <NoSSR>
68
+ <Link href={pathname} replace style={styles.link}>
69
+ {url}
70
+ </Link>
71
+ </NoSSR>
57
72
 
58
73
  <Link href="/_sitemap" replace style={[styles.link, { marginTop: 8 }]}>
59
74
  Sitemap
package/types/jest.d.ts CHANGED
@@ -3,6 +3,7 @@ declare global {
3
3
  namespace jest {
4
4
  interface Matchers<R> {
5
5
  toHavePathname(pathname: string): R;
6
+ toHaveSegments(segments: string[]): R;
6
7
  toHaveSearchParams(params: Record<string, string>): R;
7
8
  }
8
9
  }