Giter Club home page Giter Club logo

expo-next-react-navigation's Introduction

🥳 This library has moved to solito!

Exciting news: the future of this library will be solito. I completely changed the API and approach into something much more scalable and simple, so I decided to make a new library altogether.

This change is a result of my thinking on #68 about the future API.

If you're already using expo-next-react-navigation, don't stress. You can continue to use it as you please. But I recommend using solito, even if you're incrementally adopting it.

I have tons of code using expo-next-react-navigation in my own app, and I won't be able to change it overnight. But going forward, all new navigation code I write will use solito instead.

While this repo will of course stay up, I will probably no longer make updates to expo-next-react-navigation.


Expo + Next.js Router + React Navigation 🥳

A set of hooks that wrap the react-navigation API that you're used to, and make it work with next/router.

This library helps me use the Expo + Next.js integration without stressing about navigation.

Next.js Conf

Screen Shot 2021-10-22 at 3 00 05 PM

I'm speaking at Next.js Conf 2021 on October 26 about React Native + Next.js. Get your ticket to see how we do it.

Example

👾 Github Repo | 💻 Website | 📱 Open expo app directly | ☎️ Expo app website

Install

For react-navigation v5 or v6:

yarn add expo-next-react-navigation

For react-navigation v4

Table of contents

Set up

Before continuing, I highly, highly recommend using this monorepo as your starter.

The steps below are copied from Expo's docs essentially.

However, the monorepo above is much more updated, and it works with Next.js 11, Webpack 5, and React Navigation v6 😎

If you use the monorepo, you don't need to do the setup below.


Step 0. Install next with expo:

  • Init: expo init (or npx create-next-app)

  • Install: yarn add @expo/next-adapter

  • Install next: yarn add next

  • Configure: yarn next-expo

  • Start: yarn next dev

I recommend becoming familiar next's architecture with expo. Follow the Expo docs or see this article by Evan Bacon if you're curious.

Step 1. Edit/create next.config.js

yarn add next-compose-plugins next-fonts next-images next-transpile-modules

Step 2: edit next.config.js to look something like this:

/* eslint-disable @typescript-eslint/no-var-requires */
const { withExpo } = require('@expo/next-adapter')
const withFonts = require('next-fonts')
const withImages = require('next-images')
const withPlugins = require('next-compose-plugins')

const withTM = require('next-transpile-modules')([
  'expo-next-react-navigation',
  // you can add other modules that need traspiling here
])

module.exports = withPlugins(
  [withTM, withFonts, withImages, [withExpo, { projectRoot: __dirname }]],
  {
    // ...
  }
)

All done! Run yarn next dev & open http://localhost:3000 👻

You can add other packages that need transpiling to the transpileModules array. See this post for details.

Usage

Replace the following instances in your code after installation and setup:

useNavigation 👉 useRouting

-import { useNavigation } from 'react-navigation-hooks'
+import { useRouting } from 'expo-next-react-navigation'

useLayoutEffect

-import { useLayoutEffect } from 'react-navigation-hooks'
+import { useLayoutEffect } from 'expo-next-react-navigation'

<TouchableOpacity /> 👉 <Link />

-import { TouchableOpacity } from 'react-native'
+import { Link } from 'expo-next-react-navigation'

-<TouchableOpacity onPress={() => navigate({ routeName: 'chat' })}>
-  <Text>Go</Text>
- </TouchableOpacity>
+<Link routeName="chat" params={{ roomId: 'hey!' }}>
+  Go
+</Link>

All set ⚡️

API

useRouting

React hook that wraps useNavigation (from react-navigation) hook and useRouter (from next-router).

It follows the same API as useNavigation.

import { useRouting } from 'expo-next-react-navigation'

export default function App() {
  const { navigate, push, getParam, goBack } = useRouting()


}

navigate

Only argument is a dictionary with these values. Unlike react-navigation, this doesn't currently support a string as argument.

  • routeName: string, required
  • params: optional dictionary
  • web: Optional dictionary with added values for web, following the API from next/router's Router.push function.
    • path: (optional) Fulfills the same value as pathname from next/router, overriding the routeName field. If you set this to /cars, it will navigate to /cars instead of the routeName field. As a result, it will load the file located at pages/cars.js.
    • as: (optional) If set, the browser will show this value in the address bar. Useful if you want to show a pretty/custom URL in the address bar that doesn't match the actual path. Unlike the path field, this does not affect which route you actually go to.
    • shallow: Update the path of the current page without rerunning getStaticProps, getServerSideProps or getInitialProps. Defaults to false

Example: Navigate to a user

export default function Home() {
  const { navigate } = useRouting()

  // goes to yourdomain.com/user?id=chris
  const onPress = () =>
    navigate({
      routeName: 'user',
      params: { id: 'chris' },
    })

  // 👇or this👇
  // goes to `yourdomain.com/user/chris`
  const navigateCleanLink = () =>
    navigate({
      routeName: 'user',
      params: { id: 'chris' },
      web: { as: `/user/chris` },
    })

  // 👇or this👇
  // 'profile' path overrides 'user' on web, so it uses the pages/profile.js file
  // even though it navigates to yourdomain.com/profile?id=chris?color=blue`
  // ...it actually shows up as yourdomain.com/@chris in the URL bar.
  const navigateCleanLinkWithParam = () =>
    navigate({
      routeName: 'user',
      params: { id: 'chris', color: 'blue' }, // accessed with getParam in the next screen
      web: { as: `/@chris`, path: 'profile' },
    })
}

This follows the next pattern of dynamic routing. You'll need to create a pages/user/[id].js file.

For more thoughts on how and when you should use the web field, see Web Thoughts.

getParam

Same API as getParam from react-navigation.

Similar to query from next/router, except that it's a function to grab the values.

pages/user/[id].js

Imagine you navigated to yourdomain.com/user/chris on web using the example above.

export default function User() {
  const { getParam } = useRouting()

  const id = getParam('id') // chris

  // do something with the id
}

useFocusEffect

See react navigation docs. On web, it simply replaces the focus effect with a normal effect hook. On mobile, it is the exact react navigation hook.

Make sure to use useCallback as seen in the example.

import { useFocusEffect } from 'expo-next-react-navigation'

export default ({ userId }) => {
  useFocusEffect(
    useCallback(() => {
      const unsubscribe = API.subscribe(userId, user => setUser(user))

      return () => unsubscribe()
    }, [userId])
  )

  return <Profile userId={userId} />
}

Link

The following will use the chat route in react navigation.

However, it will use the pages/room.js file for nextjs. Also, it will show up as domain.com/messages in the address bar.

Optionally accepts a nextLinkProps prop dictionary and touchableOpacityProps dictionary as well.

export default function Button() {
  return (
    <Link
      routeName="chat"
      params={{ roomId: '12' }}
      web={{
        path: '/room',
        as: 'messages',
      }}
    >
      Chat in room 12
    </Link>
  )
}

Required props:

Optional props

  • web: A dictionary with the follwing options:
type Web = {
  /**
   * Alternative path to override routeName on web.
   */
  path?: string
  /**
   * A custom URL ending to show in the browser address bar instead of the `web.path` or `routeName`.
   *
   * Should start with `/`.
   */
  as?: string
  /**
   * Prefetch the page in the background. Defaults to `true`
   */
  prefetch?: boolean
  /**
   * Scroll to the top of the page after a navigation. Defaults to `true`
   *
   */
  scroll?: boolean
  /**
   * Replace the current history state instead of adding a new url into the stack. Defaults to `false`
   */
  replace?: boolean
  /**
   * Update the path of the current page without rerunning getStaticProps, getServerSideProps or getInitialProps. Defaults to false
   */
  shallow?: boolean
}
  • web: dictionary, see useRouting().navigate docs. On v1.0.5+, you can also pass the prefetch, replace, and scroll booleans here, from the next/link component.

  • touchableOpacityProps: extends React Native's TouchableOpacity props.

  • nextLinkProps: extends next/router's Link props.

  • isText: if false, you can set the children to be non-Text nodes. Defaults to true. If true, the children can be a string or a Text node.

Other shout outs

nextjs-progressbar

I think this is an awesome package for adding a loading progress bar to your next pages. It's super easy. Check it out.

Link: https://www.npmjs.com/package/nextjs-progressbar

yarn add nextjs-progressbar

or

npm i nextjs-progressbar

pages/_app.js

import React from 'react'
import App from 'next/app'
import NextNprogress from 'nextjs-progressbar'

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props
    return (
      <>
        <NextNProgress
          color="#29D"
          startPosition="0.3"
          stopDelayMs="200"
          height="3"
        />
        <Component {...pageProps} />
      </>
    )
  }
}

export default MyApp

Web Thoughts

The web prop in the navigate function and Link component can help provide cleaner urls (user/mike instead of user?id=mike) on web.

Also, navigation patterns on mobile can be different than web, and this field can help you account for those situations.

For instance, imagine you have a tab navigator. Say the first tab has a nested stack navigator with an inbox screen and a chat room screen. If you navigate from a notifications tab to this tab, and a chat room screen was already open, you probably want that chat room to stay open on mobile. Only if you press the tab button a second time should it pop back to the inbox screen.

This may not be the case on web. Web navigation patterns on web may lead you to want to open the inbox directly, instead of the open chat screen. This example could look something like this:

navigate({
  routeName: 'inboxStack',
  web: {
    path: 'inbox',
  },
})

I've also considered letting the web field take a dynamic parameter like this chat/:roomId:

// goes to `yourdomain.com/chat/chris` and still passes `chris` as a `roomId` param
const navigateCleanLink = () =>
  navigate({
    routeName: 'chat',
    params: { roomId: 'chris' },
    web: { dynamic: `chat/[roomId]` },
  })

// goes to yourdomain.com/chat?roomId=chris
const onPress = () =>
  navigate({
    routeName: 'chat',
    params: { roomId: 'chris' },
  })

But that's not added. For now, the same is achieved by doing this:

const roomId = 'chris'

const navigateToChatRoom = () =>
  navigate({
    routeName: 'chat',
    params: { roomId },
    web: { path: `chat/${roomId}` },
  })

This would open the pages/chat/[roomId].js file, with roomId as a param.

expo-next-react-navigation's People

Contributors

cmaycumber avatar dependabot[bot] avatar evanbacon avatar jonjamz avatar nandorojo avatar remyd 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

expo-next-react-navigation's Issues

Any reason not to upgrade to Next 12?

Noticed 32 dependabot PRs and was just wondering if anything is holding this up from getting Next 12's sweet SWC perf gains. (sorry for being a nuisance)

[web] Support nested query parameters.

This works on web:

const { navigate } = useRouting()

navigate({
  routeName: 'profile',
  params: {
    id: 'fernando'
  }
})

This does not work on web:

const { navigate } = useRouting()

navigate({
  routeName: 'profile',
  params: {
    profile: { id: 'fernando', photoUrl: 'some-url.com' }
  }
})

Maybe we JSON.stringify the params on web if there are nested objects to add support for this.

Why

next/router doesn't support a nested query (vercel/next.js#2530).

Complications

Stringifying the request could look really ugly in the URL. Perhaps we console.warn someone if they pass a nested object, but don't pass a web.as. The web.as prop in the navigate function lets you show a different URL in the address bar, and could fix the ugly URL problem.

Error: Couldn't find a navigation object. Is your component inside a screen in a navigator?

Hi, I am getting this error(SS below) while trying to use react-navigation 5.

Screenshot 2021-07-05 at 2 22 19 PM

And below is the sample code.

import 'react-native-gesture-handler';
import React from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  // useColorScheme,
  View,
  Text,
  Button,
} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';

import {Colors, Header} from 'react-native/Libraries/NewAppScreen';

// import DemoComponent from '@shared/components/demo';
// import Demo1Component from '@shared/components/demo1';
import { useRouting } from 'expo-next-react-navigation';

function HomeScreen() {
  const { navigate } = useRouting();

  return (
    <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
      <Text>Home Screen</Text>
      {/* <Link style={{ color: 'green', fontSize: 20 }} routeName="Details">
        Go to Details :)
      </Link> */}
      <Button
        title="Go to Details"
        onPress={() => navigate({routeName: 'Details'})}
      />
    </View>
  );
};

function DetailsScreen() {
  // const isDarkMode = useColorScheme() === 'dark';
  const { goBack } = useRouting();

  // const backgroundStyle = {
  //   backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  // };

  return (
    <SafeAreaView >
      <StatusBar barStyle={'light-content'} />
      <ScrollView
        contentInsetAdjustmentBehavior="automatic"
      >
        <Header />
        <View
          style={{
            backgroundColor: Colors.white,
          }}>
          {/* <DemoComponent />
          <Demo1Component /> */}
          <Text>Details Page</Text>
        </View>
      </ScrollView>
      <Button title="Go back" onPress={() => goBack()} />
    </SafeAreaView>
  );
}

const Stack = createStackNavigator();

const App = () => {
  
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName='Home'>
        <Stack.Screen
          name="Home"
          component={HomeScreen}
          options={{title: 'Overview'}}
        />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default App;

babel error: Duplicate __self prop found.

You are most likely using the deprecated transform-react-jsx-self Babel plugin. Both __source and __self are automatically set when using the automatic runtime. Please remove transform-react-jsx-source and transform-react-jsx-self from your Babel config.

babel.config.js

// @generated: @expo/[email protected]
// Learn more: https://github.com/expo/expo/blob/master/docs/pages/versions/unversioned/guides/using-nextjs.md#shared-steps

module.exports = { presets: ["@expo/next-adapter/babel"] };

Add Preact compatibility/other libraries

It would be nice to be able to add compatibility for other libraries such as Preact.

Option 1

Make a new library for each router. expo-preact-react-navigation, etc.

This feels a bit redundant, but it would reduce the need for installing unnecessary dependencies.

Option 2

Re-name this library to something like expo-web-navigation and add extensible support for different web navigation libraries.

App.js

People using this library would add this to the root of their app:

import { initRouter } from 'expo-web-navigation'

initRouter({ package: 'next' })

And the following would be changed in the actual library:

init-router.ts

type Packages = 'next' | 'preact' | ...

export const Config: { package: Packages } = {
  package: 'next'
}
export function initRouter({ package }) {
  Config.package = package
}

Link.web.tsx

import { Config }  from '../init-router'

type LinkProps = {
   ...
}

const PreactLink = (props: LinkProps) => ...

const NextLink = (props: LinkProps) => ...

let Link = NextLink

if (Config.package === 'preact') {
  Link = PreactLink
}

export default Link

A similar pattern could be followed for other files ending in .web.ts

In practice, it would probably export something like this:

make-extensible.ts

import { Config }  from '../init-router'

function makeExtensibleForLibrary(packages: { next: any, preact: any, ...etc }) {
  const chosenPackage = Config.package
  return packages[chosenPackage]
}

Link.web.tsx

const Link = {
  preact: PreactLink,
  next: NextLink
}

export default makeExtensibleForLibrary(Link)

Missing docs on how to make it work with createSwitchNavigator and nested routes

The component <Link> doesn't seem to work with nested routes using createSwitchNavigator. Also not sure how to implement pages when there are nested routes, seems not easy to just export the component of the screen, what if there is underlying data on the root app like stores, etc...

Check out the implementation here:
https://github.com/skyhitz/mobile/blob/am/include-web-app/modules/marketing/web/NavBar.tsx#L52

Deployment:
https://ui-figibmu4a.now.sh/

Link component doesn't work outside of React Navigation screen

Hi,
I'm try to implement the react-router-v5. It working well with next.js.
But with expo mobile (ios simulator) this error raised:

Error:` Couldn't find a navigation object. Is your component inside a screen in a navigator?

This error is located at:
    in ForwardRef (at Link/index.js:5)
    in ForwardRef (at Home.tsx:50)
    in RCTScrollContentView (at ScrollView.js:1038)
    in RCTScrollView (at ScrollView.js:1151)
    in ScrollView (at createNativeWrapper.js:80)
    in NativeViewGestureHandler (at createNativeWrapper.js:79)
    in ComponentWrapper (at Home.tsx:39)
    in RCTView (at src/index.tsx:63)
    in SafeAreaView (at Home.tsx:38)
    in Home (at SceneView.tsx:98)
    in StaticContainer
    in StaticContainer (at SceneView.tsx:89)
    in EnsureSingleNavigator (at SceneView.tsx:88)
    in SceneView (at useDescriptors.tsx:125)
    in RCTView (at BottomTabView.tsx:38)
    in SceneContent (at BottomTabView.tsx:122)
    in RCTView (at ResourceSavingScene.tsx:44)
    in RCTView (at ResourceSavingScene.tsx:27)
    in ResourceSavingScene (at BottomTabView.tsx:117)
    in RCTView (at screens.native.js:131)
    in ScreenContainer (at BottomTabView.tsx:101)
    in RCTView (at BottomTabView.tsx:100)
    in SafeAreaProviderCompat (at BottomTabView.tsx:99)
    in BottomTabView (at createBottomTabNavigator.tsx:41)
    in BottomTabNavigator (at TabBar.tsx:16)
    in EnsureSingleNavigator (at BaseNavigationContainer.tsx:268)
    in ForwardRef(BaseNavigationContainer) (at NavigationContainer.tsx:39)
    in ThemeProvider (at NavigationContainer.tsx:38)
    in ForwardRef(NavigationContainer) (at TabBar.tsx:15)
    in TabBar (at App.tsx:9)
    in ThemeProvider (at Index.tsx:51)
    in RCTView (created by Context.Consumer)
    in StyledNativeComponent (created by Styled(RCTView))
    in Styled(RCTView) (at Index.tsx:50)
    in RNCAppearanceProvider (at src/index.tsx:70)
    in AppearanceProvider (at Index.tsx:46)
    in ThemeProvider (at Index.tsx:45)
    in RNCSafeAreaView (at src/index.tsx:26)
    in SafeAreaProvider (at Index.tsx:44)
    in Index (at App.tsx:8)
    in Main (at withExpoRoot.js:26)
    in RootErrorBoundary (at withExpoRoot.js:25)
    in ExpoRoot (at renderApplication.js:40)
    in RCTView (at AppContainer.js:101)
    in DevAppContainer (at AppContainer.js:115)
    in RCTView (at AppContainer.js:119)
    in AppContainer (at renderApplication.js:39)

useNavigation
    useNavigation.tsx:16:16
useRouting
    index.js:4:37
React.forwardRef$argument_0
    index.js:28:42
renderRoot
    [native code]:0
runRootCallback
    [native code]:0
dispatchAction
    [native code]:0
useCallback$argument_0
    use-swr.js:158:12
_callee$
    AppEntry.bundle?platform=ios&dev=true&minify=false&hot=false:144649:23
tryCatch
    runtime.js:45:44
invoke
    runtime.js:274:30
tryCatch
    runtime.js:45:44
invoke
    runtime.js:135:28
PromiseImpl.resolve.then$argument_0
    runtime.js:145:19
tryCallOne
    core.js:37:14
setImmediate$argument_0
    core.js:123:25
callImmediates
    [native code]:0
flushedQueue
    [native code]:0
callFunctionReturnFlushedQueue
    [native code]:0

My package.json

{
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "build": "next build",
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "next": "next dev",
    "eject": "expo eject",
    "format": "prettier --write \"**/*.{json,md,ts}\""
  },
  "dependencies": {
    "@react-native-community/async-storage": "^2.0.0-rc.2",
    "@react-native-community/masked-view": "0.1.5",
    "@react-navigation/bottom-tabs": "^5.2.3",
    "@react-navigation/native": "^5.1.2",
    "@react-navigation/stack": "^5.2.4",
    "@react-navigation/web": "^1.0.0-alpha.9",
    "@tryghost/content-api": "^1.3.8",
    "deepmerge": "^4.2.2",
    "expo": "^36.0.2",
    "expo-next-react-navigation": "^1.0.2",
    "next": "@canary",
    "next-compose-plugins": "^2.2.0",
    "next-fonts": "^1.0.3",
    "next-images": "^1.3.1",
    "next-offline": "^5.0.0",
    "next-transpile-modules": "^3.1.0",
    "react": "16.13.1",
    "react-dom": "16.13.1",
    "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.1.tar.gz",
    "react-native-appearance": "^0.3.3",
    "react-native-gesture-handler": "~1.5.0",
    "react-native-markdown-renderer": "^3.2.8",
    "react-native-reanimated": "~1.4.0",
    "react-native-render-html": "^4.2.0",
    "react-native-safe-area-context": "0.6.0",
    "react-native-screens": "2.0.0-alpha.12",
    "react-native-web": "^0.11.7",
    "react-native-webview": "7.4.3",
    "styled-components": "^5.0.1",
    "styled-system": "^5.1.5",
    "swr": "^0.1.18"
  },
  "devDependencies": {
    "@babel/core": "^7.9.0",
    "@expo/next-adapter": "^2.0.14",
    "@types/react": "^16.9.25",
    "@types/react-native": "^0.61.23",
    "babel-preset-expo": "^8.0.0",
    "eslint-config-airbnb": "^18.1.0",
    "eslint-config-prettier": "^6.10.1",
    "eslint-plugin-react": "^7.19.0",
    "husky": "^4.2.3",
    "native-testing-library": "^4.0.0",
    "prettier": "^2.0.1",
    "pretty-quick": "^2.0.1",
    "typescript": "^3.8.3"
  },
  "private": true,
  "husky": {
    "hooks": {
      "pre-commit": "pretty-quick --staged"
    }
  }
}

My entry point is :

import React from 'react'

import { Index } from './src/Index'
import { Navigation } from './src/navigations/Home'

export default function Main() {
  return (
    <Index>
      <Navigation />
    </Index>
  )
}

And my `src/navigations/Home``

import React from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'

import { Post } from 'src/screens/Post'
import { WebWiewScreen } from 'src/screens/Webview'

import { TabBar } from './TabBar'

const Stack = createStackNavigator()

export function Navigation() {
  return (
    <NavigationContainer>
      <Stack.Navigator headerMode="none">
        <Stack.Screen component={TabBar} name="main" />
        <Stack.Screen component={Post} name="post" />
        <Stack.Screen component={WebWiewScreen} name="Webview" />
      </Stack.Navigator>
    </NavigationContainer>
  )
}

Finally in my Home component just try to make link like:

import { Link } from 'expo-next-react-navigation'
[...]
 <Link
    key={i}
    routeName="posts"
    params={{ slug: postss.slug }}
    web={{ path: `post/${postss.slug}`, as: `post/${postss.slug}` }}
>
    <Text fontFamily="heading">{postss.title}</Text>
</Link>

Thanks by advance for your help.

Sync dynamic routes with existing react-navigation URL structure

I'm trying take advantage of Next.js's dynamic routes to be able to able to use Next.js's page routing with the exact same configuration I use for linking on iOS/Android.

Basically trying to do something like this:

// [page].js
const Page = () => {
  const {
    query: { page },
  } = useRouter();
  const { navigate } = useRouting();

  useEffect(() => {
    switch (page) {
      case "my_route":
        navigate({ routeName: "my_route" });
        break;
    }
  }, [navigate, page]);

  return <App />;
};

export default Page;

Is there a better way to do this? Or is what I am doing even feasible? As much as possible, would love to be able to navigate directly to a URL but have that tie into react-navigation.

Docs error

The useRouting code example shows a navigation property, but in practice that doesn't exist, there is only navigate. This was mildly confusing as I was migrating some React Navigation code. Regardless, thank you for the awesome package!

Jon

Impossible to setup the project on Win 10

Hi nandorojo. Thank you for your work but I can't install your project on Windows 10. Could you, please, help me to solve it?

Issue reason: Impossible to set up the project on Win 10 platform.

Error message: set -eo pipefail. SyntaxError: Unexpected identifier

How to reproduce:

  1. Download the repo as a Zip folder (Win 10 Pro)
  2. extract it. Go to the project folder
  3. execute in the CMD (bash/win cmd): npm install (yarn)
  4. the error appears.

I've attached the screenshot
expo_next-react-navigationpipefail

Thanks for any help!

P.S. You mentioned I can use it without Expo so I'd like to install & use it without it because Expo is forbidden in our project

Cannot read property 'query' of null

I keep getting the this error when tryin to use useRouting on web.
image

This is my index.tsx page

// @generated: @expo/[email protected]
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import withApollo from "../lib/withApollo";
import { useRouter } from "next/dist/client/router";
import { useRouting } from "expo-next-react-navigation";

const App = () => {
  const { navigate } = useRouting();
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Welcome to Expo + Next.js 👋</Text>
    </View>
  );
};

export default App;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "center",
    alignItems: "center",
  },
  text: {
    fontSize: 16,
  },
});

Did i miss something?

useRouting() is undefined using React Navigation v5

Example: markmctamney/react-native-starter@3cfa99c#diff-95cb9be977482c4f4ee17a16ad88ee24R15

Per the console logs, while useNavigation and useRoute work as expected, useRouting() is coming back undefined when using branch v5

I tracked this back to the expo-navigation-core package and was able to fork and fix this. I believe the issue is that package is requiring @react-navigation/native as a direct dependency rather than a peer dependency, so when expo-navigation-core is imported as a dependency by this package, it is importing its own separate copy of @react-navigation/native. Then useRouting is referencing the context created by that copy, which is undefined, rather than the context created by the version of @react-navigation/native imported to the user's project.

Anyway, my fix for this was just to edit expo-navigation-core to:

  1. Remove @react-navigation/native as a dependency
  2. Add @react-navigation/native as both a peer dependency and dev dependency (still needed for internal development I believe)
  3. Rebuild & re-publish expo-navigation-core and update this package's reference to the new one

See my fix + working example after the change:
nandorojo/expo-navigation-core@v5...markmctamney:v5#diff-b9cfc7f2cdf78a7f4b91a753d10865a2

I tested that my original example and confirmed it is now working. Didn't do much testing beyond that, but just let me know if you need anything else!

TypeError: undefined is not an object (evaluating 'scene.descriptor.route.key')

Try to create a simple Navigation between 2 screens, followed the example provider.

/screens/Login.tsx

export const Login: React.FC<LoginProps> = ({ }) => {
    const { navigate } = useRouting()

    const onPress = () => {
        /* */
    }

    return (
        <Layout>
            <LoginForm onPress={onPress} />
        </Layout>
    );
}

```/screens/SignUp.tsx `

export const SignUp: React.FC<SignUpProps> = () => {
    return (
        <Layout>
            <SignUpForm />
        </Layout>
    );
}

/pages/login.tsx
export { Login as default } from '../screens/Login'

/pages/sign-up.tsx
tsx export { SignUp as default } from '../screens/SignUp'

App.tsx (Entry point of the app)

import React from 'react';
import 'react-native-gesture-handler';

import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack'

import { Login } from './screens/Login';
import { SignUp } from './screens/SignUp';

export default function App() {

  const Stack = createStackNavigator()

  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="Login">
        <Stack.Screen name="Login" component={Login} />
        <Stack.Screen name="SignUp" component={SignUp} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Then I run expo start, the error look like this:

Here is the complete error logs 


TypeError: undefined is not an object (evaluating 'route.key')

This error is located at:
    in CardContainer (at CardStack.tsx:649)
    in RNSScreen (at createAnimatedComponent.js:165)
    in AnimatedComponent (at createAnimatedComponent.js:215)
    in ForwardRef(AnimatedComponentWrapper) (at src/index.native.tsx:147)
    in Screen (at Screens.tsx:37)
    in MaybeScreen (at CardStack.tsx:642)
    in RNSScreenContainer (at src/index.native.tsx:186)
    in ScreenContainer (at Screens.tsx:20)
    in MaybeScreenContainer (at CardStack.tsx:561)
    in RCTView (at View.js:34)
    in View (at Background.tsx:13)
    in Background (at CardStack.tsx:559)
    in CardStack (at StackView.tsx:437)
    in RNCSafeAreaProvider (at SafeAreaContext.tsx:76)
    in SafeAreaProvider (at SafeAreaProviderCompat.tsx:46)
    in SafeAreaProviderCompat (at StackView.tsx:430)
    in RCTView (at View.js:34)
    in View (at StackView.tsx:429)
    in StackView (at createStackNavigator.tsx:118)
    in Unknown (at createStackNavigator.tsx:117)
    in StackNavigator (at App.tsx:17)
    in EnsureSingleNavigator (at BaseNavigationContainer.tsx:411)
    in ForwardRef(BaseNavigationContainer) (at NavigationContainer.tsx:91)
    in ThemeProvider (at NavigationContainer.tsx:90)
    in ForwardRef(NavigationContainer) (at App.tsx:16)
    in App (created by ExpoRoot)
    in ExpoRoot (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in DevAppContainer (at AppContainer.js:121)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)
    in AppContainer (at renderApplication.js:39)


TypeError: undefined is not an object (evaluating 'scene.descriptor.route.key')

This error is located at:
    in HeaderContainer (at StackView.tsx:316)
    in RCTView (at View.js:34)
    in View (at Background.tsx:13)
    in Background (at CardStack.tsx:559)
    in CardStack (at StackView.tsx:437)
    in RNCSafeAreaProvider (at SafeAreaContext.tsx:76)
    in SafeAreaProvider (at SafeAreaProviderCompat.tsx:46)
    in SafeAreaProviderCompat (at StackView.tsx:430)
    in RCTView (at View.js:34)
    in View (at StackView.tsx:429)
    in StackView (at createStackNavigator.tsx:118)
    in Unknown (at createStackNavigator.tsx:117)
    in StackNavigator (at App.tsx:17)
    in EnsureSingleNavigator (at BaseNavigationContainer.tsx:411)
    in ForwardRef(BaseNavigationContainer) (at NavigationContainer.tsx:91)
    in ThemeProvider (at NavigationContainer.tsx:90)
    in ForwardRef(NavigationContainer) (at App.tsx:16)
    in App (created by ExpoRoot)
    in ExpoRoot (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in DevAppContainer (at AppContainer.js:121)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)

package.json

"dependencies": {
    "@react-navigation/native-stack": "^6.0.7",
    "@react-navigation/stack": "^6.0.7",
   "expo-next-react-navigation": "^1.1.14"
    "next": "^11.1.0",
}

The example provided looks outdated, so is it still possible to use this library with React-navigation v6.0?

What I doing wrong that makes me get the error above?

Add support for Next Router's prefetch.

Why

It can be useful to pre-fetch pages on the browser.

How

Export useRouter().prefetch in useRouting().

Mobile

Does nothing.

const prefetch = () => {}

export const useRouting = () => {
  ...
  return { ..., prefetch }
}

Web

import { useRouter } from 'next/router'

export const useRouting = () => {
  const { prefetch } = useRouter()
  return { ..., prefetch }
}

useRouting -- TypeError: Cannot read property 'prefetch' of null

To reproduce:

Get the repo

git clone https://github.com/Ectsang/sample-expo-next-app.git

Checkout the branch

git checkout debug-userouting

Install

yarn

Run on expo web

yarn web

Expected

It should run the web app without errors

Actual

TypeError: Cannot read property 'prefetch' of null
useRouting
src/hooks/use-routing/index.web.ts:81
78 | push: navigate,
79 | goBack,
80 | popToTop,

81 | prefetch: router.prefetch,
| ^ 82 | replace,
83 | setParams,
84 | }

What should my pages/_app.tsx look like to use this?

Currently, I have:

function MyApp({ Component, pageProps }: AppProps) {
    const [showBottomNav, setShowBottomNav] = useGlobalState('showBottomNav')
    const [dimensions, setDimensions] = useState<any>(false)

    useLayoutEffect(() => {
        setDimensions(Dimensions.get('screen'))
    }, [])

    Dimensions.addEventListener('change', (d) => setDimensions(d.screen))

    return (
        <PaperProvider theme={customTheme}>
            <Head>
                <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
            </Head>
            {dimensions &&
                <View style={{
                    flex: 1,
                    top: 0,
                    left: 0,
                    height: dimensions.height,
                    width: '100%',
                    zIndex: 10,
                    position: 'absolute',
                    paddingBottom: 50
                }}>
                    <Component {...pageProps} />
                    {showBottomNav && <FooterBar />}
                </View >
            }
        </PaperProvider >
    )
}

export default MyApp

How would I replace this with the react-navigation given that I'm using nextjs and expo

getParam undefined next SSR

Hi,

using getParam with SSR is always undefined, for example (im using the @v5 package) :

import { useRouter } from "next/router";
import { useRouting } from "expo-next-react-navigation";

const { navigation, push, getParam, goBack } = useRouting();
const router = useRouter();
console.log(
"======================================>",
getParam("productId"),
router.query
);

results in:

======================================> undefined {
productId: 'tDSywgNtkwxTGDq44'
}

but the next/router gets the query object correctly.

I didn't find any method that could replace reset

In react-navigation I can "reset" the navigation.

Example:

  1. The user goes from Home to Login page
  2. The user logs in
  3. The navigation is reset and user is sent to Home again
  4. User cannot go back to Login page

I can do this with react-navigation but I didn't figure with expo-next-react-navigation. I know, on web the browser has the history, is completely different. But there is a way to do in mobile and bypass in browser?

Remix.run integration as alternative to Next?

Hi All,

Amazing work so far, massive fan of this repo!

Just throwing this out there to hear your thoughts, do you think a Remix version could made as an alternative to Nextjs.

Remix being the new SSR framework from the guys who made React Router (Its pretty amazing).

www.remix.run <----------- pure awesomness coupled with a React Native integration like this.

What do you think, would it be relatively easy to swap Next for Remix and integrate it with React Native Monorepo?

😄

The future API

I'm going to jot down some thoughts about what navigation should look like. Feedback welcome.

There are a few different concepts to deal with:

  1. Getting around from one "page" to another
  2. Opening "modals" which don't take you to another "page"
  3. Knowing what to display

1. Getting around from one page to another

I've found that the most elegant solution to get around an app is a URL. Instead of navigate('artists'), we should do linkTo('/artists').

For native, in our linking.config, we define all the mappings of URL → state. On web, we leave linking={undefined}, and defer to Next file system routing for getting around from page-to-page.

2. Modals 😱

This is a tricky one. Sometimes, modals should be reflected in URLs. But maybe there are cases where they shouldn't.

Here's the solution I think could work. Create a web-only modal stack per Next.js page. The modal should show based on the URL. Example in this tweet.

Each "modal" (which is technically a screen) would pop on top of the base screen, depending on the URL. This is all handled by Next.js' shallow routing. To close it, you just goBack. Meanwhile, on native, you just use stackPresentation: 'modal' with the native-stack.

Caveats

Are there times when you want to open modals but not change the URL? Presumably you could "shallow" route to the current pathname on web using as, but then it won't deep link there on native. I guess that's fine. Need to give it some thought.

3. What to display

React Navigation handles this on native elegantly.

On web, next router does too. Except for modals. My current method was to make a react-navigation stack per Next.js page on web. For my native-stack, I use the same screens, and make sure to give them the same name. Holy shit is that a fragile approach. Luckily TypeScript ensures that I use the same screen names (I guess), but I really don't like it. URLs are much more reliable IMO.

What's next

I think all APIs should be consolidated into URLs. Stacks on web should render per-page based on the URL. This helps with code splitting too, I suppose.

On native, you should have a single native-stack that has all of your screens. You should use linking.config to map URL → screen. If you need to push multiple of the same screen on top of each other, you should use the getId field on react-navigation's <Stack.Screen /> component.

We should get around from page-to-page like so:

<Link to="/artists/djkhaled"></Link>

// or
linkTo('/artists/djkhaled')

We can open modals like so.

openModal('edit') // this needs intellisense! 

Now that I think about it, maybe opening modals should be constrained to URLs too. The only problem is, they're less typed.

Maybe it should be this?

linkTo('/artists/djkhaled/edit')

Oof, I don't know. This is hard.

What about query parameters?

There needs to be a better abstraction for using query parameters as state.

I often want to do this:

beatgig.com/search?query=djkhaled

I end up creating a useQuery.web.ts that uses the URL params on web, and local state on native. Don't love that approach. We need something more standardized.

Support react-navigation 5

React navigation v5 appears to drop getParam in favor of the useRoute hook. Maybe a different version of this library could support v5.

There are a few other changes too, such as using key or name instead of routeName for the navigate function.

Might have to sacrifice supporting the strict typings of react-navigation v5 for the first version of this, unless someone else can help with that.

how can get Link params after refresh page?

Hello,

I can send Link params successfully but when i refresh page on browser, i can't get params and i get "Tried to get param page but it does not exist" error on console

how can get params after refresh page?

thanks

unexpected token

when I include this package and then add a link into my page component it's giving me an error.
while working fine in expo client.

bug

Package.json
{ "dependencies": { "@react-navigation/native-stack": "^6.0.3", "expo": "~42.0.0", "expo-next-react-navigation": "^1.1.14", "next": "^11.0.1", "next-offline": "^5.0.5", "next-optimized-images": "^2.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", "react-native": "https://github.com/expo/react-native/archive/sdk-42.0.0.tar.gz", "react-native-safe-area-context": "3.2.0", "react-native-screens": "~3.4.0", "react-native-web": "^0.17.1", "setimmediate": "^1.0.5" }, "devDependencies": { "@babel/core": "^7.14.6", "@expo/next-adapter": "^3.0.1", "@types/react": "^17.0.16", "@types/react-native": "^0.64.12", "eslint": "^7.32.0", "eslint-config-next": "^11.0.1", "next-compose-plugins": "^2.2.1", "next-transpile-modules": "^8.0.0", "typescript": "^4.3.5", "webp-loader": "^0.6.0" }, "scripts": { "start": "next dev", "android": "expo start --android", "ios": "expo start --ios", "web": "expo start --web", "eject": "expo eject", "build": "next build", "next-lint": "next lint" }, "private": true }
.eslintrc
{
"extends": ["next", "next/core-web-vitals"]
}

babel config

module.exports = { presets: ['@expo/next-adapter/babel'] };

Problem to build links and export project for cpanel

Hi, Many thanks for your awesome plugin,

Fortunately, I installed your plugin on expo-nextjs project successfully, i also tested your example codes on my blank project. I built android app, ios app and it work successfully , also i published my project on vercel for test it work successfully too. but when i use command "yarn export" for build project i have problem

when i upload built files (your example) on cpanel in this URL(http://test.mahanalborz.com/out/) "Click me to open profile :)" link doesn't work. its target is: http://test.mahanalborz.com/Profile and when i manually go to http://test.mahanalborz.com/out/Profile.html and i click on "👈 Go back " Button it doesn't work and nothing happen

what's problem? how can fix that? if you need i can send my test project for you

thanks

Add TypeScript types for React Navigation v5.

The groundwork for this is already there. Changes needed:

  1. In expo-navigation-core, add the arguments for useRouting's TypeScript generics.
  • Make sure that getParam takes in one of the keys as its argument for this route's params, and returns that value type.
  • Ensure that the routeName is a key from the NavigationProp, and that the params match this field as well.

Same goes for this library in useRouting/index.web.tsx.

How/Where to add analytics when using this library?

This is just a question, not a bug.. But I'm wondering where an analytics code would be added when using this library?

Does it work just like a normal NextJS app?

import { useEffect } from 'react'
import { useRouter } from 'next/router'

function MyApp({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChange = url => {
      window.gtag('config', process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS, {
        page_path: url,
      })
    }
    router.events.on('routeChangeComplete', handleRouteChange)
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events])

  return <Component {...pageProps} />
}

export default MyApp

Or is there more to consider for native environments too?

Do I need to use nextjs in order to use this router?

Helo @nandorojo , and happy new year,

I am not using nextjs, but I think that could be nice to have a router compatible that work with it in case in the futur I want to use it.

Do I need to use nextjs in order to use that router for my expo web and native project?

Best and thanks for sharing!

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.