Simple and performant navigation on iOS, Android and the web with simple and type-safe api.
Build on top of grahammendick/navigation. Check it out if you want more control over the navigation!
- New architecture (Fabric) ready
- Simple api
- 100% Type safety (routes, params, bottom tabs)
- Bundle splitting (lazy loading, smart prefetching)
- Render as you fetch on iOS, Android & web
- Works on Android, iOS & web!
- Preload data on mouseDown (or more sensitive on hover, see
<Link />
example) - Preload component on hover (on web)
- Automatic deep-linking
- Real-time light/dark mode
- Out of the box scroll restoration (on web), because screens in tab are pushed on top of each other!
- A lot of control over the web layout
- Universal links (already works, but docs need work)
- PWA documentation (already works, but docs need work)
This is an older video which used react-native-navigation so we have newer material you bottom bar in the meantime :) View video in better frame on YouTube
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
See example: reactnativeridgenavigation.com
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',
lazy(() => import('./PostScreen'),
({ id }) => {
queryClient.prefetchQuery(
queryKeyPostScreen({ id }),
queryKeyPostScreenPromise({ id }),
{ staleTime: 3000 }
);
// 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
- normal
- bottomTabs
every screen can be used within every stack. You don't have to configure screens for a stack.
Use Expo with prebuild (Expo Go is not supported since we have native libraries)
yarn add react-native-ridge-navigation navigation-react-native
or with npm
npm install react-native-ridge-navigation navigation-react-native
Support for Material You bottom bar in Android
const { withAndroidStyles } = require("@expo/config-plugins");
module.exports = function androidMaterialYouBottomBarPlugin(config) {
return withAndroidStyles(config, async (config) => {
const styleFile = config.modResults;
const appTheme = styleFile.resources.style.find(
(style) => style.$.name === 'AppTheme',
);
appTheme.$.parent = 'Theme.Material3.DayNight.NoActionBar';
return config;
});
};
"plugins": [
"./expo-plugin-android-material-you-bottom-bar"
]
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,
},
};
// App.tsx
import * as React from 'react';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import AsyncBoundary from './helpers/AsyncBoundary';
import {
createBottomTabsRoot,
createNormalRoot,
NavigationProvider,
} from 'react-native-ridge-navigation';
import { BottomRoot, NavigationRoots, screens } from './Navigator';
import routes from './Routes';
import AsyncBoundaryScreen from './helpers/AsyncBoundaryScreen';
const navigationRoot = {
[NavigationRoots.RootHome]: createBottomTabsRoot(
[BottomRoot.Home, BottomRoot.Posts, BottomRoot.Account],
{
breakingPointWidth: 500,
components: {
override: HeaderWeb,
},
}
),
[NavigationRoots.RootAuth]: createNormalRoot(routes.AuthScreen),
};
export default function App() {
return (
<SafeAreaProvider>
<NavigationProvider
screens={screens}
SuspenseContainer={AsyncBoundaryScreen}
navigationRoot={navigationRoot}
/>
</SafeAreaProvider>
);
}
See example code for the asyncboundary stuff :)
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}` }}
// linkMode="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>
Use the createLinkComponent
component to create re-usable links without render props. E.g to create a linkable button
for react-native-paper
//ButtonLink.tsx
import { Button } from 'react-native-paper';
import { createLinkComponent } from 'react-native-ridge-navigation';
const ButtonLink = createLinkComponent(Button);
export default ButtonLink;
<ButtonLink
to={routes.PostScreen}
params={{ id: '2' }}
mode="contained"
// all optional RNP props
// all optional Link props
>
Go further
</ButtonLink>
Alternatives (push, replace, refresh or fluent)
const { push, replace, refresh, fluent } = 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}`
});
// call this if you want to refresh the screen with new params
refresh(routes.PostScreen, {
id: `${item.id}`
});
// normal root, replace full history
fluent(
fluentRootNormal(NavigationRoots.RootAuth),
fluentScreen(Routes.HomeScreen, {}),
fluentScreen(Routes.PostScreen, { id: '10' }),
fluentScreen(Routes.PostScreen, { id: '20' }),
fluentScreen(Routes.PostScreen, { id: '30' })
);
// bottom root, replace full history
fluent(
fluentRootBottomTabs(NavigationRoots.RootHome, BottomRoot.Account),
fluentScreen(Routes.HomeScreen, {}),
fluentScreen(Routes.PostScreen, { id: '10' }),
fluentScreen(Routes.PostScreen, { id: '20' }),
fluentScreen(Routes.PostScreen, { id: '30' })
);
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={{}} />;
All available properties
const {
pop, // go back
switchRoot,
preload, // call preload (done automatic on link mouseDown
push, // calls preload + pushes screen
replace, // calls preload + replaces screen
refresh, // calls preload + replaces params of screen
} = useNavigation()
const { switchToTab, currentTab } = useBottomTabIndex();
const { updateBadge, badges } = useBottomTabBadges();
// updateBadge(BottomRoots.Projects, '10');
// switchToTab(BottomRoot.Posts);
If you want a nested stack e.g. in a modal, check out the example code .
import {
NavigationNestedProvider,
ModalBackHandler,
} from 'react-native-ridge-navigation';
<ModalBackHandler>
{(handleBack) => (
<Modal
visible={modalVisible}
style={{ backgroundColor: theme.colors.background }}
statusBarTranslucent={true}
presentationStyle="pageSheet"
animationType="slide"
onRequestClose={() => {
if (!handleBack()) setModalVisible(false);
}}
>
<NavigationNestedProvider>
{/* you can render your children here and push to all registered screens*/}
<View style={{ height: 250, backgroundColor: 'pink' }}>
<Header title="Modal stack" />
<Button onPress={onClose}>Close modal</Button>
<Link to={routes.PostScreen} params={{ id: '2' }}>
{(linkProps) => <Button {...linkProps}>Account</Button>}
</Link>
</View>
</NavigationNestedProvider>
</Modal>
)}
</ModalBackHandler>
You have to enable url schemes etc in your app and it'll work!
If you want to deep link into a bottom tab or other screen you can use the fluent navigation in development mode and it will log the required url to go to the right screen :)
// global
createLinkComponent,
SwitchRoot,
BottomTabLink,
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,
StatusBar,
FocusAwareStatusBar,
NavigationRoot,
createNavigation,
createBottomTabsRoot,
createNormalRoot,
registerScreen,
createScreens,
defaultTheme,
setTheme,
getTheme,
createSimpleTheme,
setPreloadResult, // should not need this as it is done automatically
// common hooks
useParams,
useNavigation,
// extra
useBottomTabIndex,
useBottomTabBadges,
useBottomTabRefresh,
useTheme,
useIsFocused,
useFocus,// same as useNavigating
useNavigating,
useNavigated,
useUnloaded,
usePreloadResult // e.g. usePreloadResult(routes.PostScreen)
If you want to use a component globally with the navigation context pass them as children of the NavigationProvider
<NavigationProvider {...}>
<UpdateBottomTabBadgeSubscriber />
</NavigationProvider>
On the web version we need to disable window scroll since else it will sometimes use windows scroll instead of the scrollview Add this to your css
body {
overflow: hidden;
}
See the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
-
Simple form library for React Native with great UX for developer and end-user react-native-use-form
-
Smooth and fast cross platform Material Design date and time picker for React Native Paper: react-native-paper-dates
-
Smooth and fast cross platform Material Design Tabs for React Native Paper: react-native-paper-tabs
-
Simple translations in React ( Native): react-ridge-translations
-
Simple global state management in React (Native): react-ridge-state