Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation #1

Closed
wants to merge 51 commits into from
Closed

Navigation #1

wants to merge 51 commits into from

Conversation

axeldelafosse
Copy link
Owner

@axeldelafosse axeldelafosse commented Sep 24, 2021

Hey!

Here is an example of how to handle navigation in your Expo and Next.js universal app.

How to think about it:

  • Use URLs to name your routes
  • pages folder contains your navigators
  • screens folder contains your screens
  • Next.js <Component /> is passed as a screen in the bottom tab bar
  • NavigationContainer linking contains a mapping of your navigators and screens

Known issues on Next.js:

  • Tree shaking issue with React Native Web caused by React Navigation and React Native Bottom Sheet
  • Dripsy anonymous arrow function causing Fast Refresh issues (edit: fixed with "dripsy": "^3.1.0")

@cmaycumber
Copy link

Do you have any recommendations on how to handle auth/unauthed state?

Right now I think I'm going to try to render a protected and unprotected navigator based on the current user state where the BottomTabNavigator is being rendered in the Navigation component.

Interested to see if you have any insight on this.

@axeldelafosse
Copy link
Owner Author

Yeah, this is a good way to do this! You'll need to create another linking configuration and another navigator. Here is an example in the Navigation component:

export function Navigation({ Component, pageProps }: NextNavigationProps) {
  const userId = useUserId();
  const trackedLinking = useRef(userId ? linking : onboardingLinking);
  const linkingConfig = useLinkingConfig(trackedLinking);

  return (
    <NavigationContainer
      linking={linkingConfig.linking}
      onReady={linkingConfig.onReady}
    >
      <LinkTo />
      {userId ? (
        <BottomTabNavigator Component={Component} pageProps={pageProps} />
      ) : (
        <OnboardingNavigator Component={Component} pageProps={pageProps} />
      )}
    </NavigationContainer>
  );
}

Add support for turning `/artist/[slug]` → `/artist/djkhaled`, assuming that the query indeed has `{ slug: 'djkhaled' }`.
@nandorojo
Copy link
Collaborator

nandorojo commented Jan 13, 2022

After implementing the more "headless" form of navigation, I'm almost fully convinced this is the way.

Next Router on Web, React Navigation on Native. All navigation is triggered from URLs, with a useRouter() hook and Link/LinkText provided by solito.

I'll also include docs on how build a link component with animations from MotiPressable while maintaining browser accessibility.

Since we're using URLs to get around, you'll need to implement your own linking config. However, you won't need any of the hacks from this PR. Just a normal linking config. On Web, you won't even import the NavigationContainer (or any code from @react-navigation for that matter.) The pages/ folder will do the work for you.

You'll have full control over your web UI to implement yourself. If you've followed this far, you already have the tools needed to implement this yourself. All that's really left is to 1) release the hooks/components under solito, and 2) document the userland patterns, like headers, back buttons, SEO meta tags, static props, scrolling, shallow page modals, safe area, etc.

For example, I noticed SafeAreaProvider was a massive hit to my bundle size, but provided no value on Web, so I just made it a no-op. Then, for my web-only bottom tabs, I added paddingBottom: 'env(safe-area-inset-bottom) to match Twitter's mobile browser bottom tab behavior.

If you're coming from a Next.js background, you will essentially have to learn nothing new on the Web side. On the native side, you just have to set up your linking config, and then you can organize your stacks/tabs/drawers however you'd like. I'll probably also recommend pathpida/blitz route manifest to codegen functions for your URL paths.

@cmaycumber
Copy link

How do you currently have types implemented are you using pathpida? We implemented a custom solution; I'm curious if there'd be a way to get some relation between the next pages and the linking configuration so it's harder to mess up the navigation.

@nandorojo
Copy link
Collaborator

nandorojo commented Jan 13, 2022

Yeah, I'm using pathpida. That would definitely be nice. I've been using pathpida + parseNextPath to extract the actual URL. And the screen names from the linking config also are typed thanks to react navigation's global type declarations.

I really like the Blitz API for codegen'd paths. I asked Brandon if he'd extract them to a separate lib here.

So the only missing piece is the actual URL paths you put in your linking config. For now, I've been using strings, but maybe it could be something like:

import { Routes } from './routes-manifest' // pseudo-code

const linking = {
  config: {
    screens: {
      artistsTab: {
        screens: { artists: Routes.artists() }
      }
    }
  }
}

Where it gets trickier is when you have a pathname that looks like /artists/[slug]. In these cases, I've been using parseNextPath, which turns that into /artists/drake. So for linking config, we could pass :slug as the query param. It would be really cool if we could make a linking config generator that forced you to use string literals if you wrote : at the beginning of your linking config.

But at this point, we're just adding type optimizations on top of react navigation.

So anyway, I'd have something like this for dynamic screens:

import { Routes } from './routes-manifest' // pseudo-code

const linking = {
  config: {
    screens: {
      artistsTab: {
        screens: { artists: Routes.artists(), artist: parseNextPath(Routes.artist({ slug: ':slug' })) }
      }
    }
  }
}

I'm currently doing the above quite often, except that I'm using pathpida instead of the blitz API. Pathpida's API is much uglier, so I won't use it in the example above.

This is my code for parseNextPath, which is used under the hood for solito to fire navigation events on native.

import type { NextRouter } from 'next/router'

const parseNextPath = (from: Parameters<NextRouter['push']>[0]) => {
  let path = (typeof from == 'string' ? from : from.pathname) || ''

  // replace each instance of [key] with the corresponding value from query[key]
  // this ensures we're navigating to the correct URL
  // it currently ignores [...param]
  // but I can't see why you would use this with RN + Next.js
  if (typeof from == 'object' && from.query && typeof from.query == 'object') {
    const query = { ...from.query }
    for (const key in query) {
      if (path.includes(`[${key}]`)) {
        path = path.replace(`[${key}]`, `${query[key] ?? ''}`)
        delete query[key]
      }
    }
    const hasParams = Object.values(query).filter(
      (value) => value != null
    ).length
    if (hasParams) {
      path += '?'
      for (const key in query) {
        if (query[key] != null) {
          path += `${key}=${query[key]}&`
        }
      }
      if (path.endsWith('&')) {
        path = path.slice(0, -1)
      }
    }
  }

  return path
}

export { parseNextPath }

@nandorojo
Copy link
Collaborator

nandorojo commented Jan 14, 2022

Looks like Evan's auto navigation (now written with Webpack) is also statically adding navigationOptions to screen components inside the pages/ folder. Since that's what I proposed in my comment above, I'm confident this is the right approach. Even native will have a pages/ folder soon, and it will set navigation options the same way as Web. We're so close to having the best of both worlds on all platforms.

https://t.co/dqGjOjhzmV

@ghost ghost mentioned this pull request Jan 17, 2022
@jcarraway
Copy link

I took two weeks off, had a dream that separating concerns between routers was the answer, then came back to find this. Gotta love it. If you need any help with docs, testing, etc. let me know - happy to pitch in.

@nandorojo
Copy link
Collaborator

I took two weeks off, had a dream that separating concerns between routers was the answer, then came back to find this. Gotta love it. If you need any help with docs, testing, etc. let me know - happy to pitch in.

I've been using it for about a week now and it's really, really great. Feels very future-proof. I'll probably outline next steps soon for getting the community involved.

@nandorojo
Copy link
Collaborator

For those interested, I went on React Native Radio to discuss Expo / Next.js: https://twitter.com/jonesaustindev/status/1484268542325317632

@nandorojo
Copy link
Collaborator

nandorojo commented Feb 1, 2022

My new library, solito, which will take over expo-next-react-navigation, has started integrating broadly into BeatGig's app. It's working really well. If anyone wants to try it early, let me know.

@nandorojo
Copy link
Collaborator

nandorojo commented Feb 12, 2022

As I've integrated Solito into BeatGig, I have slowly solved all of the problems.

While I plan to keep Solito open source, I'm considering making a premium monorepo starter. This way, I can justify spending time on documenting and showcasing the many different patterns and use-cases.

A premium monorepo would be an example app that contains the following (and perhaps more):

  • authentication
  • navigation
    • stacks
    • tabs
  • modals
  • redirects/rewrites
  • design system setup (or bring-your-own)
  • recommended monorepo structure
  • web layouts (like headers, footers, sidebar nav)
  • animations
  • setOptions, useIsFocused, etc.
  • scroll views (when to use window scrolling vs. ScrollView)

I'm tossing this out here to see if there would be interest. I feel confident in laying out "best practices", but it's a lot of work to put it all into a maintainable monorepo starter.

The paid monorepo's issues/discussions could also be a way for people to share ideas and methods they've used for React Native + Next.js.

I just had the idea for a paid monorepo in the shower, so I haven't thought through all the details. But I think it could be a good way forward.

It would also help me keep it updated with the latest versions (Expo SDK, Next versions, etc) if there is some sort of ongoing subscription payment to it.

Would you be interested in a premium Expo + Next.js monorepo starter? lmk!

@codinsonn
Copy link

@nandorojo Great Idea! Been thinking of doing the same thing for a while, but my monetisation plan would be:

  • First 20, 50 or 100 sales of the template repo are either free or pay what you want (gets the word out)
  • Ask those beta testers how many hours this likely helped them save (helps determine sale value)
  • Price accordingly (e.g. Bedrock by Max Stoiber)
  • Turn back into "pay what you want" when a hard goal of sales income is reached. (Calculated based on how many hours you've already out into making the template + estimated maintenance cost + any donations you'd like to do to the used techs maintainers, e.g. Evan Bacon, Expo Team, yourself included)

Obviously it's all up to you in the end, but that's my two cents on how I'd try to monetise my own starter template after basically years and years of iteration 🤷‍♂️

@deman4ik
Copy link

So will be this PR updated with Solito?

@nandorojo
Copy link
Collaborator

So will be this PR updated with Solito?

Yes.

@marklawlor
Copy link

marklawlor commented Feb 24, 2022

@nandorojo What about doing a sponsor only repository? https://github.blog/2022-02-02-new-sponsors-only-repositories-custom-amounts-and-more/

@corysimmons
Copy link

Would you be interested in a premium Expo + Next.js monorepo starter?

Would love to support your work if it's not too expensive of a subscription. I think minimum $5/mo, max $10/mo.

-- Poor Guy

@deman4ik
Copy link

deman4ik commented Mar 1, 2022

So will be this PR updated with Solito?

Yes.

Do you have any particular date? Starting a new project with that stack and due to lack of documentation I have a lot of confusion about the right way of integrating it.

@corysimmons
Copy link

corysimmons commented Mar 1, 2022

@deman4ik I'm figuring out the whole Solito stack (not sure what to call it, but Solito, Dripsy, and Moti. @nandorojo should give the stack a proper name and maybe a GH org) as we speak and will upload a minimal (no bottom sheet, etc...) boilerplate repo very soon. I'm not sure if that helps anyone, but I'll link it anyway since I watched his conf presentation, was super excited, and then thoroughly confused as how to set everything up.


Update: Here https://github.com/team-nimble/smd

It's a pretty barebones template of a Yarn Workspaces Next/Expo monorepo using Solito, Moti, and Dripsy. I did add Electron to it.

Update2: Use Fernando's examples! https://github.com/nandorojo/solito/tree/master/example-monorepos/blank

@nandorojo
Copy link
Collaborator

nandorojo commented Mar 3, 2022

Thanks for that, @corysimmons. Looks like a good boilerplate starter.

There are plans on my end to add full-scale docs (plenty of which are already up at solito.vercel.app 🤫), as well as examples with the many use-cases discussed on this thread.

I'm busy with many work things currently, so my time is constrained. I have ongoing conversations with companies who rely on this stack and may sponsor my work. If I end up doing that, it could speed things up, so I'll try to update here when I know more.

Things like @corysimmons making the example are an awesome way to help in the meantime.

Thanks guys! Hope that adds some context for now.

@nandorojo
Copy link
Collaborator

Hey guys. The solito docs are almost ready. I haven't publicly announced it yet, but you can take a look here if you're interested:

solito.dev

Thanks for all the help and suggestions along the way. I'll follow up when I announce solito on Twitter (please wait to tweet it out until then).

It's all coming along!

@nandorojo
Copy link
Collaborator

Solito is released! 🥳 https://twitter.com/FernandoTheRojo/status/1503457235929862154

Please like/retweet that to show support and to get the word out. Solito also has a minimal starter repo now:

npx create-solito-app@latest

I think we're good to close this PR!

@axeldelafosse maybe we should direct people to the solito starter monorepo rather than this monorepo going forward to avoid confusion. Although we may need to port over some of this repo's features first.

Can't wait to see what you all build with solito.

@axeldelafosse
Copy link
Owner Author

Woohoo! 🥳

Congrats Fernando -- thank you for your hard work and for setting this up properly.

maybe we should direct people to the solito starter monorepo rather than this monorepo going forward to avoid confusion

Yes of course!

Although we may need to port over some of this repo's features first

Which features do you have in mind?

@deman4ik
Copy link

Same features like Drawer, Tab, List-Detail and Modal navigation examples would be great!

@nandorojo nandorojo closed this Apr 4, 2022
@tonymckendry
Copy link

As someone who has been visiting this PR religiously since November, thank you so much everyone for your contributions and especially @nandorojo for the inspiration and Solito!!!!

@axeldelafosse
Copy link
Owner Author

Feel free to send me a message if you are using this stack! We are always looking for feedback.

@gregol
Copy link

gregol commented Jul 21, 2022

Maybe an Off-topic message

Hello, I would like to integrate @vercel/commerce with Solito. Do you have some idea how to do that? I am trying to do it but I got errors like this. Cannot find module '@framework/customer/use-customer' or its corresponding type declarations. I believe that is something with the configuration but I couldn't resolve it.

@nandorojo
Copy link
Collaborator

maybe you can open a discussion on Solito, but I would start with a minimal repo using Solito’s starter and adding the least amount of commerce code to show what’s not working there

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.