Giter Site home page Giter Site logo

expo / router Goto Github PK

View Code? Open in Web Editor NEW
1.4K 20.0 110.0 27.31 MB

[ARCHIVE]: Expo Router has moved to expo/expo -- The File-based router for universal React Native apps

Home Page: https://docs.expo.dev/routing/introduction/

TypeScript 92.64% JavaScript 5.15% CSS 0.01% Kotlin 0.36% Ruby 0.17% Swift 1.67%
react-native expo react-navigation react

router's Introduction

Expo Router

Warning

The Expo Router repo has moved upstream to expo/expo.

This repo will remain in maintenance-mode until Expo Router v3 is released.


Stable version 2 is out now!

Repo for the new File-based router for React Native apps. Please open a discussion if you have any questions or feedback.

Running

The easiest way to try Expo Router is by creating a new project:

npx create-expo-app@latest -e with-router

See the setup guide for more.

Examples

Contributing

router's People

Contributors

aanckar avatar alitnk avatar amandeepmittal avatar amorriscode avatar andrew-levy avatar blainekasten avatar boavistaludwig avatar brentvatne avatar brunocrosier avatar bycedric avatar danielmark0116 avatar dylancom avatar ecreeth avatar evanbacon avatar fahmitech avatar firasrg avatar frankcalise avatar frontendtony avatar g3r4n avatar gabrieldonadel avatar jacerchetoui56 avatar jacqueslg avatar karlhorky avatar kei95 avatar marklawlor avatar matzielab avatar muneebahmedayub avatar mzaien avatar olalonde avatar simek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

router's Issues

`NativeStack.Screen` doesn't apply `options` inside of dynamic path

I have the following structure:

  • (root).tsx โ†’ NativeStack
  • (root)
    • index.tsx
    • users
      • [id].tsx

If I use NativeStack.Screen inside of (root)/index.tsx to set options such as the title, it works fine. However, if I do the same inside of (root)/users/[id].tsx, the screen options don't apply.

If I apply the screen options inside of the (root).tsx file as a child of NativeStack, it does in fact work.

Reproduction here.
The line that doesn't work properly is here.

I noticed that options are only being set in an effect, so I'm not sure why it doesn't work.

Aside

It's probably worth documenting that people should memoize options or lift it out of render, since it's a dependency of a hook which calls setOptions under the hood.

Stack.Screen and useLink need differently formatted paths

Summary

When working with <Stack.Screen> and useLink, we need to modify the values of paths we're using between them to make them work. Given this file path: /folder/index.tsx:

// this works
<Stack.Screen name='folder/index' />
// this does not
<Stack.Screen name='folder' />
const link = useLink()
// cannot be found
link.push('/folder/index')
// this works
link.push('/folder')

I would expect both values would work in both contexts.

Edit: sorry about closing/reopening, accidentally hit the button :)

Path linking does not working properly on windows.

Summary

If you try to use it on windows, the path-linking system goes crazy and links the files to \\{routename}.

Here is an example:

linking {"config": {"screens": {".\\index": ".\\index", "[...404]": "*", "_sitemap": "_sitemap"}}, "getInitialURL": [Function getInitialURL], "getPathFromState": [Function getPathFromState], "getStateFromPath": [Function getStateFromPath], "prefixes": ["exp://192.168.0.14:19000/--/"], "subscribe": [Function addEventListener]}

Minimal reproducible example

Create a new expo app and install the expo-router, create a new route and you can see the issue.

Navigating to details screen in a nested Stack requires full path

Problem

From /tabs/schedule/index, unable to Link to details or /details via href string or href {{ screen: "details" }}.

image

| - app
| -- (root)
| --- tabs
| ---- schedule.js (stack w/ Index and Details)
| ---- schedule
| ----- index.js
| ----- details.js

Solution

If I link directly to /tabs/schedule/details it screen will be pushed on the stack properly

Example here: https://github.com/frankcalise/expo-router-tabs-demo/blob/main/app/(root)/tabs/schedule/index.js

feat: bundle splitting

There are two types of bundle splitting in Metro:

  • Development: this is useful for bundling much faster by skipping modules that aren't required for the initial load.
  • Production: useful for web loading speeds. This will require a custom Metro serializer.

This is not blocking the v1 release.

feat: rewrites config

We need a configuration for rewriting incoming paths to new destinations. This will require the following:

  1. New config file that is JS-only (perhaps expo.config.js?). This could potentially be worked into app.config.js too ๐Ÿค”
  2. Ability to fetch the config from expo-router. The rewrites will need to be saved as application-side logic on native.
  3. Matching system for rewrites. Need to match slugs and various other URL glob conventions.
  4. Intercept URL + Apply changes in getStateFromPath.

This feature should be considered blocking for the stable release.

ENG-6594

`require.context` is not enabled

Screen Shot 2022-09-27 at 6 43 46 PM

I don't see a special step for this in the docs (other than resolving metro's version). yarn why metro resolves 0.72.3 which is correct, so maybe this additional step should be documented?

For context, this is my original metro file:

// Learn more https://docs.expo.io/guides/customizing-metro
/**
 * @type {import('expo/metro-config')}
 */
const { getDefaultConfig } = require('expo/metro-config')
const path = require('path')

const projectRoot = __dirname
const workspaceRoot = path.resolve(__dirname, '../..')

const config = getDefaultConfig(projectRoot)

config.watchFolders = [workspaceRoot]
config.resolver.nodeModulesPaths = [
  path.resolve(projectRoot, 'node_modules'),
  path.resolve(workspaceRoot, 'node_modules'),
]

module.exports = config

Maybe this would have worked had I not specified a custom config?

3 levels deep nested router inheriting parent layout

Summary

When a stack layout is nested inside a parent tabs layout, the nested stack screen is showing up under tabs list rather than starting it's own navigation pattern.

Minimal reproducible example

app/ 
   tabs/ 
       stack/ // stack 2
          _layout.js
          index.js
     tab1.js
     tab2.js 
 _layout.js // stack 1
index.js     

In above file system, stack 2 is showing up under tabs screen rather than starting its own navigation context.

feat: pre-render static pages

Expo CLI should support rendering multiple HTML files, not just index.html. Running npx expo export for web should crawl the app directory, and statically render each file into a unique html file that will be output in the dist directory.

Motivation

  • Better web SEO for some pages. This is useful for improving iOS spotlight search results.
  • Slightly closer to removing the need to redirect all requests to the root index.html. To completely remove this we will need support for dynamic routes, which is out of scope for this feature.

Requirements

  • This feature will require at least introducing externals support to Metro. This will enable us to render a bundle with Metro while keeping react intact (required for hooks).
  • Expo CLI implementing the Expo Router manifest spec and pre-rendering with Metro.
  • Probably more is needed, but this is where the feature is currently blocked.

demo app erroring out

Demo app is not running. After yarn install & yarn start getting this error

Metro has encountered an error: While resolving module `expo-router/build/exports`, the Haste package `expo-router` was found. However the module `build/exports` could not be found within the package. Indeed, none of these files exist:

  * `/Users/roro/Programming/react-native/expoRouterDemoApp/packages/expo-router/build/exports(.native|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx|.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|.ios.json|.native.json|.json)`
  * `/Users/roro/Programming/react-native/expoRouterDemoApp/packages/expo-router/build/exports/index(.native|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx|.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|.ios.json|.native.json|.json)`: /Users/roro/Programming/react-native/expoRouterDemoApp/node_modules/metro-resolver/src/resolve.js (199:9)

  197 |     candidates,
  198 |   };
> 199 |   throw new MissingFileInHastePackageError(opts);
      |         ^
  200 | }
  201 |
  202 | class MissingFileInHastePackageError extends Error {

ABI46_0_0RCTFatal
__37-[ABI46_0_0RCTCxxBridge handleError:]_block_invoke
_dispatch_call_block_and_release
_dispatch_client_callout
_dispatch_main_queue_drain
_dispatch_main_queue_callback_4CF
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRunLoopRun
CFRunLoopRunSpecific
GSEventRunModal
-[UIApplication _run]
UIApplicationMain
main
start_sim
0x0

Tabs/Stack/Drawer configs from screens dont work, when app starts from deep link

possibility modify change tabs/stack options from screen are beautiful. like

import { Tabs } from "expo-router";
import { View } from "react-native";

export default function Page() {
  return (
    <View>
      <Tabs.Screen options={{ title: "Home" }} />
      ...

but its not working, if app starts from deep link. for example, when i start demo app from exp://127.0.0.1/--/settings i got:

the solution for now is to set the options for the tabs in the parents, like

<Tabs>
  <Tabs.Screen name="settings" options={{ title: "Settings" }} />
  <Tabs.Screen name="index" options={{ title: "Home" }} />
</Tabs>

Nested dynamic routes

Sorry for bringing up the complicated use cases on day one already ๐Ÿคฃ

I'm looking to access a slug of a dynamic route that's higher up in the hierarchy. So imagine a route like this:

myapp://folders/[folderId]/files/[fileId]

Now, in [fileId].js I'd like to access both $folderId and $fileId.

What I've already tried:

Given the route myapp://folders/[folderId]/files, route.params.folderId will not be availabe in files.js. But I can do the following in [folderId].js to make it availabe:

<Drawer.Screen
  name="files"
  initialParams={{ routes.params.folderId }}
/>

However, as soon as I have another dynamic segment (fileId), this will no longer work and the only thing I can access in [fileId].js is the route.params.fileId, not route.params.folderId (although React Navigation should shallow merge the initialParams with the params provided in navigation).

Any ideas on this?

docs: passing param page is out of date

Hi! I was playing around with this library and found that Passing parameters to the routes is out of date with the latest expo-router dependency. The Link API from the library had a different definition in the app where href property expects the following in the docs;

      <Link
        href={{
          screen: "details",
          /* 1. Navigate to the details route with query params */
          params: { itemId: 86, otherParam: "anything you want here" },
        }}
      >

But the Href type actually expects below as mentioned in docs file;

      <Link
        href={{
          pathname: "details",
          // /* 1. Navigate to the details route with query params */
          query: { itemId: 86, otherParam: "anything you want here" },
        }}
      >

package.json

{
  "name": "expo-router-playground",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "expo": "~46.0.16",
    "expo-router": "^0.0.29",
    "expo-status-bar": "~1.4.0",
    "react": "18.0.0",
    "react-native": "0.69.6",
    "react-native-safe-area-context": "4.3.1",
    "react-native-screens": "~3.15.0"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9"
  },
  "overrides": {
    "metro": "0.73.1"
  },
  "private": true
}

Incorrect tabs getting rendered on `0.0.30`

Sorry for using @next version, couldn't help myself. I'm getting this behavior there:

Screen Shot 2022-10-19 at 6 04 42 PM

I have app/_layout and app/index.tsx.

index gets rendered as duplicate tabs, and [...404] seems to get added there on its own. The second index tab is opening the sitemap.

That's actually pretty useful, but I'm curious what this behavior is meant to be.

Support expo-constants in Metro web

Summary

{Constants.manifest} is {} on web platform, so there is for example no way to get the current version number via {Constants.manifest.version}

Minimal reproducible example

npx create-react-native-app -t with-router

Input expoconstants when asked for the app name.

cd expoconstants

yarn expo install expo-constants

yarn web

touch app/index.js

Then in App.js add

import { Text } from 'react-native'
import Constants from 'expo-constants'

export default function Home() {
  return (
    <Text>{JSON.stringify(Constants.manifest)}</Text>
  )
}

The `redirect` prop on `Stack.screen` doesn't work as described in docs

Summary

The docs have an example for protecting all routes in the (app) folder where a code comment reads:

// When the auth is unavailable (no user signed in), restrict access to all the routes in the (app) directory.

During my testing the redirect prop will only restrict access to the index page in the app directory here so a sub route within (app) will remain active if they click a button to log out on that page.

You can see in the following video that when i click "Sign out" i am not redirected to the sign-up page.

CleanShot.2022-11-14.at.19.14.15.mp4

I'm wondering if the cited comment from the docs is incorrect here and whether the expectation is that we would manually add a <Stack.Screen /> entry for every route? Or perhaps it should be doing what the comment suggests and there is a bug in the router?

Either way, I'd be super grateful of some guidance here on the recommended way to protect routes ๐Ÿ™

Minimal reproducible example

https://github.com/jjenzz/expo-router-bug

nested stack nuance - potential issue

hey, i'm not sure if this is an issue or whether it's something wrong with my setup but this is my file setup

app/
  (home).tsx
  (home)/
    index.tsx
    inbox/
      index.tsx
      [id].tsx

(home).tsx is just a stack like this

import { Stack } from "expo-router";

export default Stack;

(home)/index.tsx renders a custom header with <Stack.Screen ... which is working fine

however trying to render the same custom header with Stack.Screen within inbox/index or inbox/[id] doesn't work - it renders the default react navigation header. even if i add a layout root (home)/inbox.tsx with another stack like <Stack screenOptions={{headerShown:false}} />, the default header is rendered for the inbox screens

any ideas? no clue how to proceed!

[WEB] Attempting to load assets on nested route fails

Summary

Loading assets from a 'base' route (e.g. /somepath) works correctly, loading them from /assets/[path to assets].

When attempting to load assets such as fonts from a nested path (e.g. /nested/123), the page attempts to load them from /nested/assets/[path to assets] which is incorrect and leads to the page not rendering correctly. This only happens if you visit the page URL directly, or refresh the browser while on that page.

This happens with locally-sourced assets (e.g. those hosted inside the assets folder) but also with node_modules (e.g. the expo-google-fonts packages exhibit the same behaviour).

This test was done on the web platform - I haven't explicitly tested on mobile/native as there is no way to refresh the page to trigger the bug.

For clarification, I'm unsure if this is a router-specific issue or a bundler issue. Please move to the appropriate repo if this is not the correct one.

Minimal reproducible example

https://github.com/ChronSyn/expo-router_bug-report-assetpath

Unable to resolve module `@react-navigation/core`

Unable to resolve module @react-navigation/core from /Users/fernandorojo/Developer/madison-hacks/zeeg/node_modules/expo-router/build/link/useLinkToPath.js: @react-navigation/core could not be found within the project or in these directories:
  ../../node_modules/expo-router/node_modules
  ../../node_modules
  ../../../node_modules
  ../../../../../node_modules
  
  ../../node_modules
> 1 | import { getActionFromState, getStateFromPath, NavigationContainerRefContext, } from "@react-navigation/core";
    |                                                                                       ^
  2 | import { LinkingContext } from "@react-navigation/native";
  3 | import * as React from "react";
  4 | import { Linking } from "react-native";

Honestly surprised to see this error.

Screen Shot 2022-10-06 at 8 00 08 PM

This is what I see in my node_modules ^. @react-navigation/core is a dependency of /native, but it seems like it isn't getting resolved. Maybe because it's too many levels deep?

Screen Shot 2022-10-06 at 8 01 13 PM

It is in a monorepo, so maybe this has something to do with it. Not sure. I don't have any react navigation packages installed manually anywhere. This is my package.json:

{
  "name": "zeeg-example",
  "description": "Example app for zeego",
  "version": "0.6.0",
  "main": "index.js",
  "scripts": {
    "android": "react-native run-android",
    "ios": "expo run:ios",
  },
  "dependencies": {
    "@radix-ui/react-context-menu": "^0.1.6",
    "@react-native-menu/menu": "^0.5.2",
    "add": "^2.0.6",
    "expo": "^46.0.0",
    "expo-router": "^0.0.25",
    "react": "18.0.0",
    "react-dom": "16.13.1",
    "react-native": "0.69.6",
    "react-native-gesture-handler": "~2.5.0",
    "react-native-ios-context-menu": "^1.14.0",
    "react-native-reanimated": "~2.9.1",
    "react-native-safe-area-context": "4.3.1",
    "react-native-screens": "~3.15.0",
    "react-native-web": "^0.17.5"
  },
  "devDependencies": {
    "@babel/core": "^7.18.6",
    "@babel/runtime": "^7.9.6",
    "babel-loader": "^8.2.3",
    "babel-plugin-module-resolver": "^4.0.0",
    "babel-preset-expo": "~9.2.0",
  }
}

`useBottomTabBarHeight` doesn't work

Summary

import {
  useBottomTabBarHeight,
} from '@react-navigation/bottom-tabs'

Try calling useBottomTabBarHeight in a screen and it'll break, rather than giving you a number.

Error: Couldn't find the bottom tab bar height. Are you inside a screen in Bottom Tab Navigator?

Minimal reproducible example

Let me know if this is needed.

_$$_REQUIRE.context is not a function

Screen Shot 2022-10-18 at 4 48 55 PM

Hey! I didn't see a troubleshooting item for this, so figured I'd create an issue as others may have run into this as well.

My new expo-router app, created via npx create-react-native-app -t with-router, immediately crashes due to _$$_REQUIRE.context is not a function.

Troubleshooting steps followed

  • I confirmed that EXPO_ROUTER_APP_ROOT is defined โ€“ you can even see its value (the argument to require.context) in the error message in my screenshot.
  • It also appears to be different from "require.context is not enabled" from #10.

The steps in troubleshooting don't seem to help โ€“ you can see in the package.json contents below that expo's version is ^46.0.16 (I tried pinning 46.0.13 as well, which didn't help), and there is no metro.config.js in the app root.

This app lives in a monorepo, but I don't think that's affecting this, since dependency versions are managed via syncpack to prevent mismatches, and there are indeed no mismatches in the monorepo.

Any ideas would be greatly appreciated!

package.json

{
  "name": "@genesis/app-contrib-react-native",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "expo start --dev-client",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "expo": "^46.0.16",
    "expo-dev-client": "~1.3.1",
    "expo-router": "~0.0.28",
    "expo-splash-screen": "~0.16.2",
    "expo-status-bar": "~1.4.0",
    "react": "18.0.0",
    "react-dom": "18.0.0",
    "react-native": "0.69.6",
    "react-native-gesture-handler": "~2.5.0",
    "react-native-reanimated": "^2.10.0",
    "react-native-safe-area-context": "4.3.1",
    "react-native-screens": "~3.15.0",
    "react-native-web": "^0.18.9"
  },
  "devDependencies": {
    "@babel/core": "^7.18.6"
  },
  "resolutions": {
    "metro": "^0.73.1",
    "metro-resolver": "^0.73.1"
  },
  "overrides": {
    "metro": "^0.73.1",
    "metro-resolver": "^0.73.1"
  }
}

nested stacks and fragment routes with deeplinks

Summary

hello! in our app we have a tab navigator with 3 nested stacks with screens that can be deep linked to.

our home stack uses a layout route so instead of /home and /home/profile we use / and /profile to mirror navigation in our web app (we're using solito so routes have to be identical across native and web).

if I deep link to a route in one of the other stacks (community/questions/123) then do router.back(), i am taken to the /community route as expected

however, if i deep link to a route in the home stack (/profile) and do router.back() I get

The action 'GO_BACK' was not handled by any navigator.

Is there any screen to go back to?

This is a development-only warning and won't be shown in production.

I've attached a minimal repro with setup that resembles ours as closely as possible

I have tried setting this behaviour up by doing home/profile instead of (home)/profile and it seems to work as expected - is there any way to achieve this with fragment routes so that doing router.back() after navigating to /profile takes you to /?

thanks so much in advance

Minimal reproducible example

https://github.com/louisholley/expo-router-issue-repro

  1. open the app with npx uri-scheme open "exp://192.168.0.15:19000/--/community/questions/123" --ios and see that pressing back in the header works
  2. open the app with npx uri-scheme open "exp://192.168.0.15:19000/--/profile" --ios and see the error mentioned above

Always console.logging useLinkingConfig

I noticed that useLinkingConfig() is always being console.logged to the terminal when the demo app is started/reloaded, is this by design? If so, is there a way to turn off this behavior?

Logging seems to happen in ContextNavigationContainer.tsx on line 45.

expo-router/entry cannot be found when local project name is "expo-router"

Hello!

Love this so much! Just tried following the docs exactly and got this when running "expo start"

error: Error: While resolving module `expo-router/entry`, the Haste package `expo-router` was found. However the module `entry` could not be found within the package. Indeed, none of these files exist:

any ideas?

Improve Metro resolution error message

Hi,

I found that adding this

import { StacksProvider } from "@mobily/stacks";

would cause this error:

image

The reproduce repo is here: https://github.com/Albert-Gao/expo-router-web-try-out

remove the usage of <StacksProvider/> would solve this problem.

As shown in the screenshot, @mobily/stacks is not in the call stack, would be better if we can include this @mobily/stacks in the call stack so we can identify this problem easier.

<Screen /> not redirecting properly when restricting routes

Summary

When following the authentication example in the docs I am facing a redirect issue.

I have the following file structure:
Screenshot 2022-10-27 at 12 51 29

The code for restricting routes:

function RootLayout() {
  // Use some global auth context to control the route access.
  // const auth = AuthContext.useToken();
  const auth = true;

  return (
    // Create a basic custom layout to render some children routes.
    <Layout screenOptions={{ showHeader: false }}>
      <Layout.Screen
        name="auth"
        // When the auth is available (user is signed in), restrict access to the sign-in page.
        redirect={auth}
      />
      <Layout.Screen
        name="(app)/home"
        // When the auth is unavailable (no user signed in), restrict access to all the routes in the `(app)` directory.
        redirect={!auth}
      />

      <Layout.Children />
    </Layout>
  );
}

When I toggle the auth constant to false it correctly redirects me to auth/index but when I toggle the auth constant to true it redirects me to (auth)/index instead of (auth)/home/index.

Is this an issue with the router or I messed up something?

Thanks

Minimal reproducible example

All details are in the summary

[Android] _sitemap route scrollview

Summary

I was trying to navigate to the _sitemap of my project, but when I try to scroll down, it does not work. (See video)
On ios, scrolling is perfectly fine.

Minimal reproducible example

Android.Emulator.-.Pixel_4_API_29_5554.2022-10-21.22-11-45.mp4

How to type props of a route with Typescript?

Is there an exported type we can use to type the props?

In Next.js this is type like:

context.params?.id // undefined | string | string[]

app/blog/[id].js

export default function BlogPost({ route }) {
  return (
    <Text>
      {route.params.id}
    </Text>
  );
}

Unable to resolve module expo-router/entry

Getting the following error while bundling for iOS simulator. Using pnpm.

Unable to resolve module expo-router/entry from <root>/apps/expo/index.js: expo-router/entry could not be found within the project or in these directories:
  node_modules
  ../../node_modules
  node_modules
  ../../node_modules
> 1 | import "expo-router/entry"
    |         ^
  2 |

package.json:

"dependencies": {
    "app": "workspace:*",
    "expo": "46.0.15",
    "expo-router": "^0.0.25",
    "expo-splash-screen": "~0.16.1",
    "expo-status-bar": "~1.4.0",
    "react": "18.0.0",
    "react-dom": "18.0.0",
    "react-native": "0.69.6",
    "react-native-gesture-handler": "~2.5.0",
    "react-native-reanimated": "~2.9.1",
    "react-native-safe-area-context": "4.3.1",
    "react-native-screens": "~3.15.0",
    "react-native-web": "~0.18.7"
  },
  "devDependencies": {
    "@babel/core": "^7.18.6",
    "@babel/runtime": "^7.18.6",
    "@types/react": "~18.0.0",
    "@types/react-native": "~0.69.1",
    "typescript": "^4.6.3"
  }

babel.config.js, metro.config.js and index.js:
CleanShot 2022-10-07 at 17 15 31

feat: implement a router that supports shared URLs in tabs

Apps like Spotify, Twitter, Instagram, etc. all have the ability to present the same page from multiple tabs. This is achievable in React Navigation by either abandoning deep linking support, or by adding the same screen with different URLs. Neither of these would work with Expo Router.

Using the same route in different places is prevented as this would create a conflict:

app
  (tabs).js
  (tabs)
    (home)
      home.js # /home
      post.js # /post
    (search)
      search.js # /search
      post.js <- Conflict 

We need something that works more like a stack, where the user manually defines the tabs:

app
  (tabs).js # New layout that manually indicates `/home` and `/search` are initial routes
  (tabs)
    home.js
    search.js
    post.js # Since this isn't an initial route, any of the tabs can push it.

The new router would have multiple state arrays, one for every initial route.

{
  routes: ['/home', '/search', '/post'],
  state [
    { initial: '/home' stack: [] },
    { initial: '/search' stack: [] }
  ]
}
  • All <Link /> operations would push a new screen on the stack, unless the link points to an initial route, in which case it will redirect to the initial route and pop the stack.
  • The stack can be reset from anywhere by linking to the initial route name.
  • Initial route names would not be able to be pushed on other stacks (we could maybe allow this in the future by introducing a special prop like push).

Ideally, we'd drop the automatic tabs and drawers in favor of this new system where you manually choose which routes are tabs, this is much closer to how web-only frameworks work today.

Can I use /app inside /src

My question is simple.

I created a new app with the expo-router template.

After doing this I moved the /app folder into /src and fast-refresh did not update my folder.

When I reloaded the entire app it showed the touch app/index.js screen.

I wanted to know if there is any way to use it inside the /src folder.

Windows has Issues Bundling Assets on Web

Summary

This may be the wrong place to post this, but I'm not entirely sure where this issue belongs. I encountered the issue when trying to implement expo-router in one of my projects.

While I was converting over to metro from webpack in order to enable the use of expo-router, I ran into issues running the application on web. Images would always error with the message: Uncaught Error: Image: asset with ID "108" could not be found. Please check the image source or packager.

After digging in and debugging with a co-worker, it seems like this error only happens on Windows (Mac OS runs fine locally).

I also checked the Source tab in the browser and added some breakpoints to the bundle to check how assets were being loaded/used. To my surprise, it looked like the registerAsset method in the bundle wasn't even running (causing the getAsssetById method to throw an error since our asset array was empty).

This was the relevant block of code on Windows that is supposed to handle asset registration/retrieval:

__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
  exports.getAssetByID = getAssetByID;
  exports.registerAsset = registerAsset;
  var assets = [];

  function registerAsset(asset) {
    return assets.push(asset);
  }

  function getAssetByID(assetId) {
    return assets[assetId - 1];
  }
},212,[],"..\\..\\node_modules\\react-native-web\\dist\\modules\\AssetRegistry\\index.js");

After adding breakpoints, it was clear that the registerAsset method was not being invoked and the following block ended up throwing errors since the asset came back null:

  function resolveAssetUri(source) {
    var uri = null;
    if (typeof source === 'number') {
      var asset = (0, _AssetRegistry.getAssetByID)(source);
      if (asset == null) {
        throw new Error("Image: asset with ID \"" + source + "\" could not be found. Please check the image source or packager.");
      }
      var scale = asset.scales[0];
      if (asset.scales.length > 1) {
        var preferredScale = _PixelRatio.default.get();

        scale = asset.scales.reduce(function (prev, curr) {
          return Math.abs(curr - preferredScale) < Math.abs(prev - preferredScale) ? curr : prev;
        });
      }
      var scaleSuffix = scale !== 1 ? "@" + scale + "x" : '';
      uri = asset ? asset.httpServerLocation + "/" + asset.name + scaleSuffix + "." + asset.type : '';
    } else if (typeof source === 'string') {
      uri = source;
    } else if (source && typeof source.uri === 'string') {
      uri = source.uri;
    }
    if (uri) {
      var match = uri.match(svgDataUriPattern);

      if (match) {
        var prefix = match[1],
          svg = match[2];
        var encodedSvg = encodeURIComponent(svg);
        return "" + prefix + encodedSvg;
      }
    }
    return uri;
  }

Digging EVEN DEEPER, it looks like the registerAsset method that was being used by the bundler was from a completely separate module react-native/Libraries/Image/AssetRegistry => ../../node_modules/@react-native/assets/registry.js (I added breakpoints in that module and do see it running):

__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
  module.exports = _$$_REQUIRE(_dependencyMap[0], "react-native/Libraries/Image/AssetRegistry").registerAsset({
    "__packager_asset": true,
    "httpServerLocation": "/assets/../../node_modules/expo-router/assets",
    "width": 48,
    "height": 48,
    "scales": [1],
    "hash": "b2de8e638d92e0f719fa92fa4085e02a",
    "name": "forward",
    "type": "png",
    "fileHashes": ["b2de8e638d92e0f719fa92fa4085e02a"]
  });
},580,[379],"..\\..\\node_modules\\expo-router\\assets\\forward.png");

So, my next question was - why is MacOS working? I had my coworker pull his bundle and noticed that the registerAsset block referenced a different module there. His referenced the same module that I saw was actually running on Windows ../../node_modules/@react-native/assets/registry.js:

  module.exports = _$$_REQUIRE(_dependencyMap[0], "@react-native/assets/registry");
},201,[202],"../../node_modules/react-native/Libraries/Image/AssetRegistry.js");
__d(function (global, _$$_REQUIRE, _$$_IMPORT_DEFAULT, _$$_IMPORT_ALL, module, exports, _dependencyMap) {
  'use strict';

  var assets = [];

  function registerAsset(asset) {
    return assets.push(asset);
  }

  function getAssetByID(assetId) {
    return assets[assetId - 1];
  }

  module.exports = {
    registerAsset: registerAsset,
    getAssetByID: getAssetByID
  };
},202,[],"../../node_modules/@react-native/assets/registry.js");

So my assumption is that the module reference at the end of that block serves to override the module's handler so that it runs correctly. On Windows, it is trying to use ..\\..\\node_modules\\react-native-web\\dist\\modules\\AssetRegistry\\index.js, which just seems wrong. MacOS is overriding the correct module ../../node_modules/@react-native/assets/registry.js and thus doesn't have the issue.

Minimal reproducible example

https://github.com/kylegwalsh/expo-router-bundle-error-example

pathname without fragment routes

firstly, thanks for this package it's amazing!

for implementing something like determining whether a link is active, the docs have an example with const { pathname } = useHref();

however, for me if i navigate to /home this gives something like /(root)/(app)/(tabs)/home/index. is there an api for determining what the current path is based on how you would route there? like e.g. just /home instead of the above string

cheers!

The 'navigation' object hasn't been initialized yet.

Summary

Hello,

First of all, thank you for your work and excited to see the future of it!
I am facing an issue when using expo router with header buttons.

With that configuration, I get an error from react navigation : The 'navigation' object hasn't been initialized yet.
I supposed I get this from the usage of useLink or useHref. But I need this in these components.
So I tried with a Splashscreen in order to wait, but it does not change anything!

So is that an issue from expo router or in this particular case (in screenOptions) I have to use the navigation and route props (that are not homogenous) ?

Thanks!

Minimal reproducible example

Using expo 47.

// _layout.tsx
import { Stack } from 'expo-router';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { Provider } from 'react-redux';
import ProfileAndBackHeaderButton from '../src/components/ProfileAndBackHeaderButton';
import SettingsHeaderButton from '../src/components/SettingsHeaderButton';
import { store } from '../src/redux/store';

function RootLayout() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <Provider store={store}>
        <Stack
          screenOptions={({ route }) => {
            const isIndexRoute = route.name === 'index' || route.name === '/';
            const title = isIndexRoute ? null : route.name;

            return ({
              title,
              headerTransparent: isIndexRoute,
              headerTitleAlign: 'left',
              animation: 'fade',
              headerLeft: () => (
                <ProfileAndBackHeaderButton />
              ),
              headerRight: () => (
                <SettingsHeaderButton />
              ),
            });
          }}
        />
      </Provider>
    </GestureHandlerRootView>
  );
}

export default RootLayout;
// ProfileAndBackHeaderButton
import { useHref, useLink } from 'expo-router';
import { Button, ButtonSize, Colors } from 'react-native-ui-lib';
import { AntDesign, Ionicons } from '@expo/vector-icons';

function ProfileAndBackHeaderButton() {
  const href = useHref();
  const link = useLink();

  const onPressAction = () => {
    if (href.href === '/') {
      link.push('/profile');
    } else {
      link.back();
    }
  };

  return (
    <Button
      style={{ width: 40, height: 40, marginRight: 10 }}
      round
      backgroundColor={Colors.white}
      size={ButtonSize.small}
      onPress={onPressAction}
    >
      {href.href === '/'
        ? <AntDesign name="user" size={24} color="black" />
        : <Ionicons name="chevron-back-outline" size={24} color="black" />}
    </Button>
  );
}

export default ProfileAndBackHeaderButton;
// index.tsx
import { SplashScreen } from 'expo-router';
import { useEffect, useState } from 'react';
import { View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { Text } from 'react-native-ui-lib';
import Map from '../src/components/Map';

function Home() {
  const [isReady, setReady] = useState(false);

  useEffect(() => {
    // Perform some sort of async data or asset fetching.
    setTimeout(() => {
      setReady(true);
    }, 100);
  }, []);

  return (
    <>
      {!isReady && <SplashScreen />}
      <View style={{ flex: 1 }}>
        <Map />
        <SafeAreaView>
          <Text>Oui</Text>
        </SafeAreaView>
      </View>
    </>
  );
}

export default Home;

EAS Cli build error

Summary

Hi! I was trying to make a build with EAS cli, and got some errors.
One of them contains expo-router module issue.
I have expo-updates in the project, maybe they have some conflicts (take a look at the screenshot)?

Minimal reproducible example

image

feat: add support for route aliases

The linking configuration of React Navigation currently does not support aliases for a route. This means we cannot support / and /index both going to routes named index.

We could potentially support this directly in expo-router since we needed to write a custom getStateFromPath and getPathFromState.

This is currently blocking the stable release.

How to handle auth

Hi Evan,

as I said on Twitter already, awesome work on the router! I've started implementing it today and have removed sooo much code. My app has gotten a 100% more declarative. One thing I struggle wrapping my head around is auth. And I think it's such an important part of almost every app that it's worth having a best practice in the docs. I'm happy to contribute a PR for that once I've figured it out for myself. Before Expo Router, I was basically doing the following:

<Stack.Navigator>
      {isLoggedIn ? (
        <Stack.Screen name="Authenticated" component={Authenticated} />
      ) : isLoggedOut ? (
        <Stack.Screen name="SignIn" component={SignIn} />
      ) : (
        <Stack.Screen name="Loading" component={Loading} />
      )}
</Stack.Navigator>

This way, only the relevant screens (and dependents) were mounted based on the auth state. Now with file-based routing, the files will always be there. I'm sure you have thought about auth already, any best practice you have in mind?

Expo router <SplashScreen />

Hi!

I want to ask if it is a known issue or not.
I still see a white screen after the Splash screen, for a second before I can see my app mounted.
This is my file system routes
photo_5935864053018770326_y

And here is my (root).tsx file

import React from 'react';
import {Layout, SplashScreen} from 'expo-router';
import {NativeBaseProvider} from 'native-base';
import {useLanguageService} from 'src/hooks';
import useCachedResources from 'src/hooks/useCachedResources';
import {useStore} from 'src/store';
import {custom_theme} from 'src/theme/custom-theme';
import Animated, {SlideInLeft} from 'react-native-reanimated';

export default function Root() {
  //Hooks
  const isLoadingComplete = useCachedResources();
  const {changeLanguage} = useLanguageService();
  //Store selectors
  const stored_lang = useStore.useLang();
  const hasHydrated = useStore.useHasHydrated();
  const isSignedIn = useStore.useIsSignedIn();

  React.useLayoutEffect(() => {
    if (hasHydrated) changeLanguage(stored_lang);
  }, [hasHydrated]);

  return (
    <React.Fragment>
      {/* When all loading is setup, unmount the splash screen component. */}
      {!isLoadingComplete && !hasHydrated && <SplashScreen />}
      
      {isLoadingComplete && hasHydrated && (
        <Animated.View entering={SlideInLeft} style={{flex:1}} >
          <NativeBaseProvider theme={custom_theme}>
            <Layout>
              <Layout.Screen
                name="(user)"
                redirect={!isSignedIn}
                options={{
                  headerShown: false,
                }}
              />
              <Layout.Screen
                name="(auth)"
                redirect={isSignedIn}
                options={{
                  headerShown: false,
                }}
              />

              <Layout.Children />
            </Layout>
          </NativeBaseProvider>
        </Animated.View>
      )}
    </React.Fragment>
  );
}

And my useCachedResources hook

import {FontAwesome} from '@expo/vector-icons';
import * as Font from 'expo-font';
import {useEffect, useState} from 'react';

export default function useCachedResources() {
  const [isLoadingComplete, setLoadingComplete] = useState(false);

  // Load any resources or data that we need prior to rendering the app
  const loadResourcesAndDataAsync = async () => {
    try {
      // Load fonts
      await Font.loadAsync({
        ...FontAwesome.font,
        'space-mono': require('../assets/fonts/SpaceMono-Regular.ttf'),
        'Ubuntu-Bold': require('../assets/fonts/ubuntu-fonts/Ubuntu-Bold.ttf'),
        'Ubuntu-BoldItalic': require('../assets/fonts/ubuntu-fonts/Ubuntu-BoldItalic.ttf'),
        'Ubuntu-Italic': require('../assets/fonts/ubuntu-fonts/Ubuntu-Italic.ttf'),
        'Ubuntu-Light': require('../assets/fonts/ubuntu-fonts/Ubuntu-Light.ttf'),
        'Ubuntu-LightItalic': require('../assets/fonts/ubuntu-fonts/Ubuntu-LightItalic.ttf'),
        'Ubuntu-Medium': require('../assets/fonts/ubuntu-fonts/Ubuntu-Medium.ttf'),
        'Ubuntu-MediumItalic': require('../assets/fonts/ubuntu-fonts/Ubuntu-MediumItalic.ttf'),
        'Ubuntu-Regular': require('../assets/fonts/ubuntu-fonts/Ubuntu-Regular.ttf'),
      });
    } catch (e) {
      // We might want to provide this error information to an error-reporting service
      console.warn(e);
    } finally {
      setLoadingComplete(true);
    }
  };
  useEffect(() => {
    loadResourcesAndDataAsync();
  }, []);

  return isLoadingComplete;
}

And you can see the actual app in this video

Android.Emulator.-.Pixel_4_API_29_5554.2022-10-16.16-48-30.mp4

I am not 100% sure if this is an expo-router issue, sorry in advance

Unable to resolve "expo-router/entry"

I'm getting the error: Unable to resolve "expo-router/entry" from "app/index.tsx".

I have looked at #29, and it's not related to that issue it looks like. It's something that happened as soon as I tried to go from yarn to pnpm.

Skjermbilde 2022-10-21 kl  08 54 05

non-posix paths not supported

I am trying this on Windows 11.
After following the steps from the docs, I can't manage to use navigation.
I will include a short video.
I tried everything from Troubleshooting page

20220928085455.mp4

No issues on Mac OS (m1) and iOS simulator

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.