onReady={() => {}} callback called before navigationRef is set. (getCurrentRoute() undefined onReady)

Current Behavior

I am following the react native screen navigation documentation and passing a callback to onReady to set the routeNameRef.current to the current route name, however when onReady runs the callback the navigationRef hasn’t be set.

navigationRef.current?.getCurrentRoute().name is undefined when calling:

routeNameRef.current = navigationRef.current?.getCurrentRoute().name

If I call a setTimeout for 1 second then navigationRef has been set.

Expected Behavior

  • I expect the onReady callback to only be called after navigationRef is set.

My Navigation Component

import React, { useEffect, useState } from 'react'
import { NavigationContainer, DefaultTheme } from '@react-navigation/native'
import { navigationRef, routeNameRef, isReadyRef } from './RootNavigation'
import { createStackNavigator } from '@react-navigation/stack'
import RNBootSplash from 'react-native-bootsplash'
import analytics from '@react-native-firebase/analytics'

import NotFoundScreen from 'src/screens/NotFound'
import { RootStackParamList } from 'src/types'
import BottomTabNavigator from './BottomTabNavigator'
import AuthNavigator from './AuthNavigator'
import OnboardingNavigator from './OnboardingNavigator'
import LinkingConfiguration from './LinkingConfiguration'
import { useGlobalStore } from 'src/store'
import Splash from 'src/screens/Splash'

export default function Navigation () {
  const { store } = useGlobalStore()
  const [isLoading, setIsLoading] = useState(false)

  useEffect(() => {
    const initializer = async () => {
      setIsLoading(true)
      // …do multiple sync or async tasks
      setIsLoading(false)
    }

    initializer().finally(async () => {
      await RNBootSplash.hide({ fade: true })
    })

    return () => {
      isReadyRef.current = false
    }
  }, [])

  return (
    <NavigationContainer
      ref={navigationRef}
      linking={LinkingConfiguration}
      theme={DefaultTheme}
      onReady={() => {
        routeNameRef.current = navigationRef?.current?.getCurrentRoute()?.name
        isReadyRef.current = true
      }}
      onStateChange={async (state) => {
      const previousRouteName = routeNameRef.current
      const currentRouteName = navigationRef.current.getCurrentRoute().name //getActiveRouteName(state) 

        if (previousRouteName !== currentRouteName) {
          await analytics().logScreenView({
            screen_name: currentRouteName,
            screen_class: currentRouteName
          })
        }
        // Save the current route name for later comparision
        routeNameRef.current = currentRouteName
      }}
    >
      {
        store.initialized || isLoading ? <RootNavigator /> : <Splash />
      }
    </NavigationContainer>
  )
}

const Stack = createStackNavigator<RootStackParamList>()

function RootNavigator () {
  const { store } = useGlobalStore()
  const initialRouteName = store.token ? 'Root' : 'Auth'
  return (
    <Stack.Navigator initialRouteName={initialRouteName} screenOptions={{ headerShown: false }}>
      <Stack.Screen name='Auth' component={AuthNavigator} />
      <Stack.Screen name='Onboarding' component={OnboardingNavigator} />
      <Stack.Screen name='Root' component={BottomTabNavigator} options={{ cardStyle: { backgroundColor: '#fff' } }} />
      <Stack.Screen name='NotFound' component={NotFoundScreen} options={{ title: 'Oops!' }} />
    </Stack.Navigator>
  )
}

Your Environment

software version
iOS or Android iOS 13.3
@react-navigation/native “^5.7.3”
react-native “0.63.3”
node “v12.18.3”
npm or yarn yarn “1.22.10”

1 possible answer(s) on “onReady={() => {}} callback called before navigationRef is set. (getCurrentRoute() undefined onReady)

  1. You’re conditionally rendering the navigator:

    store.initialized || isLoading ? <RootNavigator /> : <Splash />

    onReady fires when the container finishes mounting (https://reactnavigation.org/docs/navigation-container/#onready). If you don’t have a navigator rendered, there’s no current route name.

    I expect the onReady callback to only be called after navigationRef is set.

    The ref is already set, you just don’t have any navigator rendered.

    Instead of conditionally rendering the navigator, you need to conditionally render the container instead.