nandorojo / expo-gatsby-navigation

🤵Make Gatsby and React Navigation play nicely together with an Expo/React Native Web app.
12 stars 0 forks source link

Expo + Gatsby + React Navigation 🤵

A set of hooks/components that wrap the react-navigation API that you're used to, and make it work with Gatsby's built-in routing.

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

react-navigation v5 is not yet supported, but I'm working on it now :)


Want to use next.js with expo instead? Check out expo-next-react-navigation.


Example

👾 Github Repo | 💻 Website

Install

yarn add expo-gatsby-navigation

For react-navigation v5:

yarn add expo-gatsby-navigation@v5

Table of contents

Set up

Install gatsby with expo:

gatsby-config.js

module.exports = {
    plugins: [
        `gatsby-plugin-react-native-web`,
        /* ... */
    ],
}

All done! Run yarn gatsby develop & open http://localhost:8000 👻

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

Usage

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

useNavigation 👉 useRouting

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

useLayoutEffect

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

<TouchableOpacity /> 👉 <Link />

-import { TouchableOpacity } from 'react-native'
+import { Link } from 'expo-gatsby-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 makes it play nicely with Gatsby's built-in routing.

It follows the same API as useNavigation.

import { useRouting } from 'expo-gatsby-navigation`

export default function Screen({ location }) {
  const { navigation, push, getParam, goBack } = useRouting()

  return <CoolComponent />
}

navigate

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

Example: Navigate to a user

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

    // goes to yourdomain.com/user
    // and sends the param to the screen component
    // see getParam() docs for more
    const onPress = () =>
        navigate({
            routeName: 'user',
            params: { id: 'chris' },
        })

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

    // 👇or this👇
    // 'profile' path overrides 'user' on web, so it uses the src/pages/profile.js file
    // even though it navigates to yourdomain.com/profile
    // this will also replace the current screen in the history
    const navigateCleanLinkWithParam = () =>
        navigate({
            routeName: 'user',
            params: { id: 'chris' }, // accessed with getParam and the location prop in the screen file
            web: { replace: true, to: '/profile' },
        })
}

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

getParam

Same API as getParam from react-navigation, with an important difference.

Important: The getParam function with gatsby is slightly less simple than the one offered by expo-next-react-navigation. You have to provide a fallback value as the second argument. This is because gatsby doesn't expose a hook to access the location.

Example: src/pages/index.js

export default function Screen({ location }) {
    const { getParam } = useRouting()
    // when on web, it will return the value for location.state.userId
    // web on mobile, it will use react-navigation
    const userId = getParam('userId', location.state.userId)
}

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 below.

import { useFocusEffect } from 'expo-gatsby-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 src/pages/room.js file for Gatsby.

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

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

Required props:

Optional props

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: {
        to: '/inbox',
    },
})