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.
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
MyNavigatorcomponent that contains our navigator logic. This is the component that's rendered when you render the navigator in your app with thecreateMyNavigatorfactory function. - We use the
useNavigationBuilderhook and pass itStackRouter, which would make our navigator behave like a stack navigator. Any other router such asTabRouter,DrawerRouter, or a custom router can be used here as well. - The hook returns the navigation state in the
stateproperty. This is the current state of the navigator. There's also adescriptorsobject 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]- asstate.indexis the index of the currently focused route in thestate.routesarray. - Then we get the corresponding descriptor for the focused route with
descriptors[focusedRoute.key]and call therender()method on it to get the React element for the screen. - We use the
renderfunction returned byuseNavigationBuilderto 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.