javascripttypescriptreact-nativedeep-linkingbranch.io

How to navigate from linking (deep linking with branch.io) when navigator hasn't been created yet?


I pretty much followed both react-navigation deep linking and branch.io react-native documentation, and either both are deprecated or just not completely helpful.

All I want is that whenever a deep link reads from the linking, navigate to a certain screen, I'm not looking to implement a listener on a specific screen, I want this on a root path, and is either the onReady (which for me didn't work) or linking from navigator container

this is my code, very simple

const linking: LinkingOptions = {
  prefixes: ['agendameio://', 'https://agendame.io', 'https://agendameio.app.link', 'https://agendameio.app-alternative.link'],
  subscribe(listener) {
    const navigation = useNavigation();
    const onReceiveURL = ({ url }: { url: string }) => listener(url);
    Linking.addEventListener('url', onReceiveURL);
    branch.skipCachedEvents();
    branch.subscribe(async ({ error, params, uri }) => {
      if (error) {
        console.error('Error from Branch: ' + error);
        return;
      }
      if (params) {
        DataManager.DynamicURL = params['~id'] === "951933826563912687" ? params.id : undefined;
      }
      let url = params?.['+url'] || params?.['~referring_link']; // params !== undefined ? `agendameio://empresa/${params.id}` : 'agendameio://empresa';
      navigation.navigate(`DetalleEmpresa${params.id}`);
      listener(url);
    });
    return () => {
      Linking.removeEventListener('url', onReceiveURL);
      branch.logout();
    };
  },

I instantly get an error due to use navigation, but I really don't know what else to use to navigate to inside the app

EDIT: this is the error in particular

Error

EDIT 2: I'll add my navigation so it can help to understand my problem

function firstStack() {
  return (
    <homeStack.Navigator initialRouteName="EmpresasScreen">
      <homeStack.Screen
        options={({navigation}) => ({
          headerShown: false,
          headerTitle: () => (
            <>
              <View style={styles.viewHeader}>
                <Image 
                  resizeMode="contain" 
                  style={styles.imageLogo} 
                  source={Images.iconoToolbar} 
                />
              </View>
            </>
          ),
        })}
        name="EmpresasScreen"
        component={EmpresasScreen}
      />
      <detalleEmpresaStack.Screen
        options={{ headerShown: false }}
        name="DetalleEmpresaScreen"
        component={DetalleEmpresaScreen}
      />
      <agendamientoStack.Screen
        options={{ headerShown: false }}
        name="AgendamientoScreen"
        component={AgendamientoScreen}
      />
    </homeStack.Navigator>
  );
}

function secondStack() {
  return (
    <misCitasStack.Navigator>
      <misCitasStack.Screen
        options={({navigation}) => ({
          headerShown: false,
          headerTitle: () => (
            <>
              <View style={styles.viewHeader}>
                <Image 
                  resizeMode="contain" 
                  style={styles.imageLogo} 
                  source={Images.iconoToolbar} 
                />
              </View>
            </>
          ),
        })}
        name="MisCitasScreen"
        component={CitasScreen}
      />
      <detalleCitasStack.Screen
        options={({navigation}) => ({
          headerShown: false,
        })}
        name="DetalleCitaScreen"
        component={DetalleCitaScreen}
      />
    </misCitasStack.Navigator>
  );
}

function tabStack() {
  return (
    <tab.Navigator
      screenOptions={({route}) => ({
        tabBarIcon: ({focused}) => {
          let iconName;
          if (route.name === 'Home') {
            iconName = focused
              ? Images.casaActive
              : Images.casa
          } else if (route.name === 'Citas') {
            iconName = focused 
              ? Images.citasActive
              : Images.citas
          }
          return <Image source={iconName} />
        }
      })}
      tabBarOptions={{
        showLabel: false,
      }}>
      <tab.Screen name="Home" component={firstStack} />
      <tab.Screen name="Citas" component={secondStack} />
    </tab.Navigator>
  );
}

function menuStackNavigator() {
  useEffect(() => {
    VersionCheck.needUpdate({forceUpdate: true}).then(async res => {
      if (res.isNeeded) {
        alertNeedUpdate(res.storeUrl, false);
      }
    });
    if(Platform.OS === 'android') {
      NativeModules.SplashScreenModule.hide();
    }
  }, [])
  return (
    <NavigationContainer linking={linking}>
      <stack.Navigator headerMode="none">
        <stack.Screen name="Home" component={tabStack} />
        <stack.Screen name="Error" component={ErrorScreen} />
      </stack.Navigator>
    </NavigationContainer>
  );
};

const styles = StyleSheet.create({
  viewHeader: {
    alignItems: 'center',
    justifyContent: 'center',
  },
  imageLogo: {
    alignItems: 'center',
    justifyContent: 'center',
    marginTop: 6,
    marginBottom: 6
  }
});

export default menuStackNavigator;

Solution

  • you can use Configuring links to open the target screen directly.

    see more example here configuring-links
    Here the URL /feed will open screen named Chat.

    import { NavigationContainer } from '@react-navigation/native';
    
    const linking = {
      prefixes: ['https://mychat.com', 'mychat://'],
      config: {
        screens: {
          Chat: 'feed/:sort', //URL `/feed` will open screen named `Chat`.
          Profile: 'user',
        }
      },
    };
    
    function App() {
      return (
        <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
          <Stack.Navigator>
            <Stack.Screen name="Chat" component={ChatScreen} />
            <Stack.Screen name="Profile" component={ProfileScreen} />
          </Stack.Navigator>
        </NavigationContainer>
      );
    }
    
    or use navigationRef.

    read about it navigating-without-navigation-prop.
    wait to navigation to be ready and then navigate.

    import { createNavigationContainerRef } from '@react-navigation/native';
    
    function App() {
    
      const navigationRef = createNavigationContainerRef();
    
      const navigateWhenNavigationReady = (routeName, params, n = 0) => {
        setTimeout(() => {
            if (navigationRef?.getRootState()) {
                navigationRef.navigate(routeName, params)
            }else if (n < 100) {
                navigateWhenNavigationReady(routeName, params, n + 1);
            }
        }, 300)
      }
    
      const linking = {
         ...,
         subscribe(listener) {
            ...
            navigateWhenNavigationReady("Chat", {id: 123});
         }
      };
    
    
      
      return (
        <NavigationContainer ref={navigationRef} linking={linking}>
          <Stack.Navigator>
            <Stack.Screen name="Chat" component={ChatScreen} />
            <Stack.Screen name="Profile" component={ProfileScreen} />
          </Stack.Navigator>
        </NavigationContainer>
      );
    
    }