4

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;
Nicolas Silva
  • 566
  • 1
  • 10
  • 28
  • We don't yet have support for `react-navigation`. Can you share the error message you're observing here? – Kartik Shandilya Aug 30 '21 at 07:16
  • @KartikShandilya my bad, post edited to include an image of the error, btw I want to clarify two things, both branch.io and react-navigation mention each other, and I quote react-navigation "Next, you would need to subscribe to incoming links from your third-party integration, For example, to get to subscribe to incoming links from branch.io:" and here from branch documentation, "Now push the view for this URL this.navigator.push({ title: title, url: url, image: image })" – Nicolas Silva Aug 30 '21 at 16:27

3 Answers3

3
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>
  );

}

Ahmed Gaber
  • 3,384
  • 2
  • 12
  • 18
  • that timeout doesn't look like a good option, wouldn't be better to use the onReady from navigationContainer? tho I'm not entirely sure how to do that – Nicolas Silva Sep 01 '21 at 21:12
  • i don't think that, setTimeout it is simple, and it tested for me in many projects, it retries to use navigation for limited times `else if (n < 100)`, but you can also use onReady – Ahmed Gaber Sep 01 '21 at 23:23
  • well, I'm getting this error "The 'navigation' object hasn't been initialized yet. This might happen if you don't have a navigator mounted" multiple lines of the same error – Nicolas Silva Sep 03 '21 at 15:05
0

can you try doing it like this

const App = () => {
  return (
    <NavigationContainer>
      <AppNavigator />
    </NavigationContainer>
  );
};

and do the subscribe in

export const AppNavigator = () => {
  const navigation =useNavigation();
 
  useEffect(()=>{
  //add listener and navigation logic here

  },[]);
 
  return (
    <Stack.Navigator >
      ...
    </Stack.Navigator>
  );
};

this will make navigation context to be available in AppNavigator Component

Muhammed Yasir MT
  • 1,894
  • 1
  • 6
  • 7
  • ok but what if i want to call the linking from outside, which is a good practice imho – Nicolas Silva Sep 01 '21 at 21:15
  • Why do you want to call subscribe from outside?? As it can only do the navigation when navigation is ready. You can create a function that does all the work in but will have to call it from the position in above code. Or you will have to create some mechanism to check if navigation is ready if not wait a few seconds do the check again... Until is ready and then use a navigation ref to navigate – Muhammed Yasir MT Sep 02 '21 at 04:02
0

Answer use custom hook useNavigationWhenReady.

const useNavigationWhenReady = (isReady, navigationRef) => {

    const [routeName, setRouteName] = React.useState();
    const [routeParams, setRouteParams] = React.useState({});
    const [navigationAction, setNavigationAction] = React.useState("navigate");

    React.useEffect(() => {
        if (isReady && routeName) {
            if(navigationRef && navigationRef[navigationAction]) {
                const _navigationAction = navigationRef[navigationAction];
                _navigationAction(routeName, routeParams);
            }
        }
    }, [isReady, routeParams, routeParams]);


    const navigate = (_routeName, _routeParams = {}) => {
        if(!routeName) {
           setNavigationAction("navigate");
           setRouteParams(_routeParams);
           setRouteName(_routeName);
        }
    };

    const reset = (state) => {
        if(!routeName) {
           setNavigationAction("reset");
           setRouteName(state);
        }
    };

    return { navigate, reset }
};

you can now use useNavigationWhenReady instead of useNavigation;

import { createNavigationContainerRef } from '@react-navigation/native';

function App() {

  const [isReady, setReady] = React.useState(false);  
  const navigationRef = createNavigationContainerRef();

  //define it here
  const navigation = useNavigationWhenReady(isReady, navigationRef);

  const handleOpenNotificationOrOpenLinking = () => {
     ...
     navigation.navigate("screenName", {param1: value1}); 
     //or use reset
     //navigation.reset({
        //index: 1,
        //routes: [{ name: 'screenName' }]
      //});
  };

  return (
    <NavigationContainer ref={navigationRef} onReady={() => setReady(true)}>

    </NavigationContainer>
  );
}

if you use react-navigation-v5 not v6 use

//const navigationRef = createNavigationContainerRef();
//const navigation = useNavigationWhenReady(isReady, navigationRef);
const navigationRef = React.useRef();
const navigation = useNavigationWhenReady(isReady, navigationRef?.current);


you can also show loading or splash screen while navigation not ready

return (
    <NavigationContainer ref={navigationRef} onReady={() => setReady(true)}>
        <RootStack.Navigator initialRouteName={isReady ? "home" : "loading"} >

       </RootStack>
    </NavigationContainer>
  );
Ahmed Gaber
  • 3,384
  • 2
  • 12
  • 18