Skip to main content

Custom navigators

In essence, a navigator is a React component that takes a set of screens and options, and renders them based on its navigation state, generally with additional UI such as headers, tab bars, or drawers.

React Navigation provides a few built-in navigators, but they might not always fit your needs if you want a very custom behavior or UI. In such cases, you can build your own custom navigators using React Navigation's APIs.

A custom navigator behaves just like a built-in navigator, and can be used in the same way. This means you can define screens the same way, use route and navigation objects in your screens, and navigate between screens with familiar API. The navigator will also be able to handle deep linking, state persistence, and other features that built-in navigators support.

tip

If you're publishing a navigator library, see Standard navigator to build a navigator that can integrate with multiple navigation libraries such as React Navigation and Expo Router.

Overview

Under the hood, navigators are plain React components that use the useNavigationBuilder hook.

The navigator component then uses this state to layout the screens appropriately with any additional UI based on the use case. This component is then wrapped in createNavigatorFactory to create the API for the navigator.

A very basic example looks like this:

import {
useNavigationBuilder,
createNavigatorFactory,
createScreenFactory,
StackRouter,
} from '@react-navigation/native';

function MyNavigator(props) {
const { state, descriptors, render } = useNavigationBuilder(
StackRouter,
props
);

const focusedRoute = state.routes[state.index];
const descriptor = descriptors[focusedRoute.key];

return render(descriptor.render());
}

export const createMyNavigator = createNavigatorFactory(MyNavigator);

export const createMyScreen = createScreenFactory();

Now, we have an already working navigator, even though it doesn't do anything special yet.

Let's break this down:

  • We define a MyNavigator component that contains our navigator logic. This is the component that's rendered when you render the navigator in your app with the createMyNavigator factory function.
  • We use the useNavigationBuilder hook and pass it StackRouter, which would make our navigator behave like a stack navigator. Any other router such as TabRouter, DrawerRouter, or a custom router can be used here as well.
  • The hook returns the navigation state in the state property. This is the current state of the navigator. There's also a descriptors object which contains the data and helpers for each screen in the navigator.
  • We get the focused route from the state with state.routes[state.index] - as state.index is the index of the currently focused route in the state.routes array.
  • Then we get the corresponding descriptor for the focused route with descriptors[focusedRoute.key] and call the render() method on it to get the React element for the screen.
  • We use the render function returned by useNavigationBuilder to render the content of the navigator with appropriate context and wrappers.

With this, we have a basic stack navigator that renders only the focused screen. Unlike the built-in stack navigator, this doesn't keep unfocused screens rendered. But you can loop through state.routes and render all of the screens if you want to keep them mounted. You can also read descriptor.options to get the options to handle the screen's title, header, and other options.