Simple, performant & type-safe cross platform navigation in React Native

react-native-ridge-navigation

Simple and performant cross platform navigation on iOS, Android and the web with simple and type-safe api for React 18 (alpha)

⚠️ This is beta software, things can break.

Things which are not finished yet:

  • useFocus (like useEffect but for when screen is focused)
  • documentation
  • create universal lazyWithPreload for screens (only on web, but callstack/repack can fix it native too)
  • universal modal (web support too)

Features

  • Superior performance (we use wix/react-native-navigation)
  • Simple api
  • Type safety
  • Type safe routes
  • Type safe params (based on url)
  • Android, iOS & web!
  • Preload data on mouseDown (or more sensitive on hover, see <Link /> example)
  • Preload component on hover (on web)
  • Automatic deep-linking
  • Lazy load components
  • Real-time light/dark mode

Example

View video in better frame on YouTube

About us

We want developers to be able to build software faster using modern tools like GraphQL, Golang and React Native.

Give us a follow on Twitter:
RichardLindhout,
web_ridge

Please contribute or donate so we can spend more time on this library

Donate with PayPal

Demo

See example: reactnativeridgenavigation.com

Source

See source of example app

Register screens

You can register screens with a preload function, the params will be automatically in string format based on the url.

// NavigatorRoutes.ts
const PostScreen = registerScreen(
  '/post/:id',
  PostScreenLazy,
  ({ id }) => {
    queryClient.prefetchQuery(
      queryKeyPostScreen({ id }),
      queryKeyPostScreenPromise({ id })
    );

    // if you return something here it can be used in the screen itself or somewhere else with
    // usePreloadResult(routes.PostScreen)
    // in this case react-query handles it based on queryKey so it's not needed but with Relay.dev it is. 
    // you can put the result of the usePreloadResult in your usePreloadedQuery if you use Relay.dev

  }
);

const routes = {
  PostScreen,
  // ...
}
export default routes

Supported stacks

  • normal
  • bottomTabs
  • sideMenu (in progress)

Installation

1. If you don't have an app yet (optional, but recommended)

Create the app with our tool web-ridge/create-react-native-web-application

npx create-react-native-web-application --name myappname

1. Install deps + library

yarn add react-native-navigation react-native-navigation-hooks react-native-ridge-navigation history && npx rnn-link && npx pod-install

or with npm

npm install react-native-navigation react-native-navigation-hooks react-native-ridge-navigation history && npx rnn-link && npx pod-install

2. Extra (optional)

Add react-native-web-image-loader (see example), this will make sure the images in the BottomBar will be in good quality on the web.

const {
  addWebpackModuleRule,
} = require('customize-cra');

// ...
  addWebpackModuleRule({
    test: /\.(png|jpe?g|gif)$/,
    options: {
      name: 'static/media/[name].[hash:8].[ext]',
      scalings: { '@3x': 1 },
    },
    loader: 'react-native-web-image-loader',
  })
// ...

Usage

import {
  createNormalRoot,
  createNavigation,
  createBottomTabsRoot,
  createScreens,
  defaultTheme,
} from 'react-native-ridge-navigation';

export const NavigationRoots = {
  RootHome: 'home',
  RootAuth: 'auth',
};

// svg to png
// https://webkul.github.io/myscale/
//
// tab icon
// http://nsimage.brosteins.com/Home
export const BottomRoots = {
  Posts: {
    path: '/post',
    title: () => 'Posts',
    icon: require('./img/palette-swatch-outline/icon-20.png'),
    selectedIcon: require('./img/palette-swatch/icon-20.png'),
    child: routes.PostsScreen,
  },
  Account: {
    path: '/account',
    title: () => 'Account',
    icon: require('./img/account-circle-outline/icon-20.png'),
    selectedIcon: require('./img/account-circle/icon-20.png'),
    child: routes.AccountScreen,
  },
};

const Navigator = createNavigation(
  defaultTheme,
  createScreens(routes),
  {
    [NavigationRoots.RootHome]: createBottomTabsRoot(
      [BottomRoots.Posts, BottomRoots.Account],
      // optional settings
      // {
      //   breakingPointWidth: 500, // default is 1200
      //   components: {
      //      start: BottomTabStart,
      //      end: BottomTabEnd,
      //   },
      // }
    ),
    [NavigationRoots.RootAuth]: createNormalRoot(routes.AuthScreen),
    // You can add endless more roots for your app e.g. for different roles of users
    // ...
  },
  AppHOC
);

export default Navigator;

New screen

Use the <Link /> component as much as possible since it will work with ctrl+click on the web :)

<Link
  to={routes.PostScreen}
  params={{ id: `${item.id}` }}
  // mode="default" // optional if sensitive the preload will be called on hover instead of mouseDown
>
  {(linkProps) => (
    <Pressable  {...linkProps}> // or other touchables/buttons
      <Text>go to post</Text>
    </Pressable>
  )}
</Link>

Alternative push (or replace)

const { push, replace } = useNavigation();

// call this where if you can't use <Link />
push(routes.PostScreen, {
  id: `${item.id}`
});

// call this if e.g. after a create you want to go to edit screen
// but without pushing history to the url-stack or app-stack :)
replace(routes.PostScreen, {
  id: `${item.id}`
});

Switch root

Switch root can be used to switch from e.g. the auth screen to a different entrypoint of your app. E.g. check the role and switch the stacks to different roots for different user roles.

<SwitchRoot rootKey={NavigationRoots.RootHome} params={{}} />;
// or e.g.
<SwitchRoot rootKey={NavigationRoots.RootAuth} params={{}} />;

useNavigation

All available properties

const {
  refreshBottomTabs, // e.g. after language change and you want to update labels
  updateBadge, // updateBadge(BottomRoots.Projects, '10');
  pop, // go back
  switchBottomTabIndex, // switch bottom tab
  switchRoot,
  preload, // call preload (done automatic on link mouseDown
  push, // calls preload + pushes screen
  replace, // calls preload + pushes screen
} = useNavigation()

Deep linking

You have to enable url schemes etc in your app and it'll work!

More

// global
  DeepLinking // see deep linking documentation
  OnlyRenderOnce // see deep linking documentation
  SwitchRoot
  Link
  BackLink // for now .pop() but we'll update this according to Android guidelines later on (to always go back in hierarchy)
  lazyWithPreload // only available on the web: see example app
  Redirect
  NavigationRoot,
  createNavigation,
  refreshBottomTabs,
  updateBadge,
  createBottomTabsRoot,
  createNormalRoot,
  createSideMenuRoot,
  registerScreen,
  createScreens,
  defaultTheme,
  setTheme,
  getTheme,
  createSimpleTheme,
  setPreloadResult, // should not need this as it is done automatically

  // hooks
  useTheme,
  useParams,
  useNavigation,
  useFocus
  usePreloadResult // e.g. usePreloadResult(routes.PostScreen)

GitHub

https://github.com/web-ridge/react-native-ridge-navigation