react navigation deep linking not working when use Tabs Stack - react-native

Version:
"dependencies": {
"react-native": "0.63.4",
"#react-navigation/bottom-tabs": "^5.11.2",
"#react-navigation/native": "^5.8.10",
"#react-navigation/stack": "^5.12.8",
}
Test website link test://info_register?token=1111 successfully, I can see route.params includes token
but when I get into my Tabs screen, and try to to use test://setting_register?token=1111, App just open it doesn't navigate to SettingScreen and route.params is undefined
I take reference from official document https://reactnavigation.org/docs/5.x/configuring-links
What is wrong with my deep linking for Tabs ?
Here is my code:
index.js
import * as React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import LoginStack from './LoginStack';
import Linking from './Linking';
const AppContainer = () => {
return (
<NavigationContainer linking={Linking}>
<LoginStack />
</NavigationContainer>
);
};
export default AppContainer;
Linking.js
const config = {
screens: {
// set config for App init screen
PersonalInfoScreen: {
path: 'info_register/',
parse: {
token: (token) => `${token}`,
},
},
// set config for Tabs screen
Setting: {
screens: {
SettingScreen: 'setting_register/:token',
},
},
},
},
};
const Linking = {
prefixes: ['test://'],
config,
};
export default Linking;
LoginStack.js
import * as React from 'react';
import {useSelector} from 'react-redux';
import {createStackNavigator} from '#react-navigation/stack';
import LoginScreen from '../screens/Login/LoginScreen';
import PersonalInfoScreen from '../screens/Login/PersonalInfoScreen';
import TabStack from './TabStack';
const Stack = createStackNavigator();
const LoginStack = () => {
const {uid, userToken} = useSelector((state) => state.LoginRedux);
const showLoginFlow = uid === '' || userToken === '' ? true : false;
return (
<Stack.Navigator
initialRouteName={'LoginScreen'}
screenOptions={{headerShown: false, gestureEnabled: false}}>
{showLoginFlow ? (
<>
<Stack.Screen name="LoginScreen" component={LoginScreen} />
<Stack.Screen
name="PersonalInfoScreen"
component={PersonalInfoScreen}
/>
</>
) : (
<>
<Stack.Screen name="TabStack" component={TabStack} />
</>
)}}
</Stack.Navigator>
);
};
export default LoginStack;
TabStack.js
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
const TabStack = () => {
return (
<Tab.Navigator
screenOptions={...mySetting}
tabBarOptions={...myStyle},
}}>
<Tab.Screen name={'Free'} component={FreeStack} />
<Tab.Screen name={'Video'} component={VideoStack} />
<Tab.Screen name={'News'} component={NewsStack} />
<Tab.Screen name={'Consultation'} component={ConsulationStack} />
<Tab.Screen name={'Setting'} component={SettingStack} />
</Tab.Navigator>
);
};
export default TabStack;

If review nested navigation https://reactnavigation.org/docs/5.x/configuring-links/#handling-nested-navigators docs.
You have this navigation tree for SettingScreen:
TabStack -> Setting -> SettingStack -> SettingScreen.
Routes configuration should match this tree also as below:
const config = {
screens: {
// set config for App init screen
PersonalInfoScreen: {
path: "info_register/",
parse: {
token: (token) => `${token}`,
},
},
// set config for Tabs screen
TabStack: {
screens: {
Setting: {
screens: {
SettingScreen: "setting_register/:token",
},
},
},
},
},
};

Just a note, it seems like whenever you are enabling debug-chrome configuration deep linking with react-navigation is not working, I disabled it and it worked!

Related

react-native Unable to retrieve params via deep link

unable to pass params from deep link. getting undefined when running:
npx uri-scheme open [prefix]://news/3 --android
NewsScreen.js
import React from 'react';
const NewsScreen = ({ route, navigation }) => {
console.log(route.params); // undefined
};
Linking.js
import LINKING_PREFIXES from 'src/shared/constants';
export const linking = {
prefixes: LINKING_PREFIXES,
config: {
screens: {
Home: {
screens: {
News: {
path: 'news/:id?',
parse: {
id: id => `${id}`,
},
},
},
},
NotFound: '*',
},
},
};
Router.js
import React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import {useAuth} from 'src/contexts/AuthContext';
import {Loading} from 'src/components/Loading';
import {AppStack} from './AppStack';
import {AuthStack} from './AuthStack';
import {GuestStack} from './GuestStack';
import SplashScreen from 'src/screens/guest/SplashScreen';
import linking from './Linking.js';
export const Router = () => {
const {authData, loading, isFirstTime, appBooting} = useAuth();
if (loading) {
return <Loading />;
}
const loadRoutes = () => {
if (appBooting) {
return <SplashScreen />;
}
if (isFirstTime) {
return <GuestStack />;
}
if (!authData || !authData.name || !authData.confirmed) {
return <AuthStack />;
}
return <AppStack />;
};
return <NavigationContainer>{loadRoutes()}</NavigationContainer>;
};
AppStack.js
import React from 'react';
import {createDrawerNavigator} from '#react-navigation/drawer';
import {SpeedNewsStack} from 'src/routes/NewsStack';
const Drawer = createDrawerNavigator();
export const AppStack = () => {
return (
<Drawer.Navigator>
<Drawer.Screen name="Home" component={NewsStack} />
</Drawer.Navigator>
);
};
NewsStack.js
import React from 'react';
import {createStackNavigator} from '#react-navigation/stack';
import SpeedNewsScreen from 'src/screens/NewsScreen';
const Stack = createStackNavigator();
export const NewsStack = () => {
return (
<Stack.Navigator>
<Stack.Screen name="News" component={NewsScreen} />
</Stack.Navigator>
);
};

Authentication flows | React Navigation v6

First i want to thank you for all answers.
I'm trying do Authentication Flow with React Navigation v6.x.
I want to catch in my App.js token value from AuthContext.js
Because of new navigation version i'm looking for new way to create a AuthFlow.
I see in Navigation documentation example for Authentication flow but there all logic be located in App.js and the whole thing is quite illegible.
I can take token from device but this solution dosent work with async changes. For example when i click SignIn button (signinscreen) it change token in mobile device but it refresh only singin screen.
App.js
import React, {useContext} from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import SigninScreen from './src/screens/SigninScreen';
import SignupScreen from './src/screens/SignupScreen';
import AccountScreen from './src/screens/AccountScreen';
import TrackListScreen from './src/screens/TrackListScreen';
import TrackDetailsScreen from './src/screens/TrackDetailsScreen';
import CreateTrackScreen from './src/screens/CreateTrackScreen';
import { Provider as AuthProvider } from './src/context/AuthContext';
import { Context as AuthContext } from './src/context/AuthContext';
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
const stackNestNav = () => {
return(
<Stack.Navigator>
<Stack.Screen name="List" component={TrackListScreen} />
<Stack.Screen name="Details" component={TrackDetailsScreen} />
</Stack.Navigator>
)
};
const AppNavigator = () => {
return(
<AuthProvider>
<NavigationContainer>
{token === null ? (
<>
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="SignIn" component={SigninScreen} />
<Stack.Screen name="SignUp" component={SignupScreen} />
</Stack.Navigator>
</>
) : (
<>
<Tab.Navigator>
<Tab.Screen name="Track List" component={stackNestNav} options={{ headerStyle: {height: 0}}}/>
<Tab.Screen name="Create" component={CreateTrackScreen} />
<Tab.Screen name="Account" component={AccountScreen} />
</Tab.Navigator>
</>
)
}
</NavigationContainer>
</AuthProvider>
)
}
export default AppNavigator;
and AuthContext.js
import AsyncStorage from '#react-native-async-storage/async-storage';
import createDataContext from './createDataContext';
import trackerApi from '../api/tracker';
const authReducer = (state,action) => {
switch(action.type){
case 'sign_error':
return {...state, errorMessage: action.payload}
case 'success_sign':
return {errorMessage: '', token: action.payload}
case 'clear_error_message':
return {...state, errorMessage: ''}
default:
return state;
}
};
//Czyszczenie wiadomości błędu
const clear_error_message = (dispatch) => () => {
dispatch({ type: 'clear_error_message' })
}
//Rejestracja
const signup = (dispatch) => async({email,password}) => {
try{
const response = await trackerApi.post('/signup', {email,password});
await AsyncStorage.setItem('token',response.data.token);
dispatch({ type: 'success_sign', payload: response.data.token });
console.log('Rejestracja przebiegła pomyślnie!');
} catch (err) {
dispatch({ type: 'sign_error', payload: 'Próba rejestracji nieudana.' })
}
};
//Logowanie
const signin = (dispatch) => async({email,password}) => {
try {
const response = await trackerApi.post('/signin', {email,password});
await AsyncStorage.setItem('token',response.data.token);
dispatch({ type: 'success_sign', payload: response.data.token });
console.log('Logowanie przebiegło pomyślnie.');
} catch (err) {
dispatch({ type: 'sign_error', payload: 'Próba logowania nieudana.' });
console.log(err.message);
}
};
//Wyloguj
const signout = (dispatch) => {
return({email,password}) => {
}
};
export const {Provider,Context} = createDataContext(
authReducer,
{signup,signin,signout,clear_error_message},
{ token: null, errorMessage: ''}
)
It is possible or should i do Context in App.js like in https://reactnavigation.org/docs/auth-flow?
One more time, thank you! :)
Solved.
exported whole NavigatoContainer content to the other Component.
created function in Context what catch token from mobile device and via dispatch updated state
in componen with whole NavigatoContainer content used state and function from point 2.
Cya!

Show Onboarding screen once

Does anyone know where I have gone wrong here?
I am trying to dispatch an action when the user taps a button that calls the onSkip function, In the reducer I set the item to local storage and I retrieve it in the rootstack component so I can conditionally set the initial screen in my stack navigator. It always returns false and goes to the onboarding screen instead of login screen.
import React, { useEffect } from 'react'
import { NavigationContainer } from '#react-navigation/native'
import { createNativeStackNavigator } from '#react-navigation/native-stack'
import { RootStackParamList } from '../interfaces/RootStackParamList'
import AsyncStorage from '#react-native-async-storage/async-storage'
import { theme } from '../themes/themes'
import { useAppSelector } from '../app/hooks'
import DrawerNav from './DrawerNav'
import Tabs from './Tabs'
import HomeScreen from '../screens/HomeScreen'
import Login from '../screens/Auth/Login'
import OnboardingScreen from '../screens/Onboarding/OnboardingScreen'
import OtpScreen from '../screens/Auth/OtpScreen'
function RootStack() {
const Stack = createNativeStackNavigator<RootStackParamList>()
const onboardingState = useAppSelector(
(state) => state.onboarding.viewedOnBoarding
)
const [onboarded, setOnboarded] = React.useState(false)
const checkOnboarding = async () => {
try {
const value = await AsyncStorage.getItem('#onBoarding')
if (value !== null) {
setOnboarded(true)
}
} catch (err) {
} finally {
}
}
useEffect(() => {
checkOnboarding()
}, [])
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName={
onboarded === true ? 'Login' : 'OnboardingScreen'
}
screenOptions={{
headerShown: false,
}}
>
<Stack.Screen options={{}} name='Login' component={Login} />
<Stack.Screen
name='OnboardingScreen'
component={OnboardingScreen}
/>
<Stack.Screen name='Tabs' component={Tabs} />
<Stack.Screen name='DrawerNav' component={DrawerNav} />
<Stack.Screen
name='OtpScreen'
component={OtpScreen}
options={{
title: 'OTP',
headerShown: true,
headerStyle: {
backgroundColor: theme.colors.whiteSmoke,
},
}}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
export default RootStack
import { createSlice, PayloadAction } from '#reduxjs/toolkit'
import AsyncStorage from '#react-native-async-storage/async-storage'
export interface onboardingState {
viewedOnBoarding: boolean
}
const initialState: onboardingState = {
viewedOnBoarding: false,
}
export const onboardingSlice = createSlice({
name: 'onboarding',
initialState,
reducers: {
setOnboardingAsync: (state, action: PayloadAction<boolean>) => {
state.viewedOnBoarding = action.payload
AsyncStorage.setItem('#onBoarding', JSON.stringify(action.payload))
},
},
})
// Action creators are generated for each case reducer function
export const { setOnboardingAsync } = onboardingSlice.actions
export default onboardingSlice.reducer
onSkip={() => (
dispatch(setOnboardingAsync(true)),
navigation.replace('Login'),
AsyncStorage.setItem('#onBoarding', JSON.stringify(true))
)}
If you want to persist user's onboarding status, just use Redux-persist in your project (no need to directly use AsyncStorage).
After you implemented redux-persist, just use the
const onboardingState = useAppSelector((state) => state.onboarding.viewedOnBoarding)
to check whether user is onboarded.

Not getting route.params when using deep linking

I'm trying to implement Deep Linking on my APP, I'm following expo-cli react-native-navigation documentation about this subject. After basic configuration I can't get params from route.params from any of the links I set.
This is an example of my code
linking.js
import * as Linking from "expo-linking";
const prefix = Linking.createURL("/");
const config = {
screens: {
userStartSession: {
path: "home/:itemInfo",
parse: { itemInfo: (itemInfo) => `${itemInfo}` },
},
},
};
const linking = {
prefixes: [prefix],
config,
};
export default linking;
Docs here: https://reactnavigation.org/docs/deep-linking/
And here: https://reactnavigation.org/docs/configuring-links/
Then import linking into Navigation.js
import React from "react";
import linking from "../utils/linking/linking";
export default function Navigation() {
return (
<NavigationContainer linking={linking} fallback={<Text>Cargando...</Text>}>
<Stack.Navigator>
<Stack.Screen
name="userStartSession"
options={{ headerShown: false, headerLeft: null }}
>
{(props) => (
<UserSessionStack
{...props}
somePropsHere={somePropsHere}
/>
)}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
UserSessionStack.js
import React, { useState, useEffect } from "react";
import { createStackNavigator } from "#react-navigation/stack";
import { useRoute } from "#react-navigation/native";
const Stack = createStackNavigator();
export default function UserSessionStack(props) {
const { somePropsHere } = props;
const route = useRoute();
console.log(route.params);
return (
<Stack.Navigator
initialRouteName="slide-home"
>
<Stack.Screen
name="slide-home"
component={SlideHome}
options={{ headerShown: false }}
/>
</Stack.Navigator>
);
}
Then following react-navigation-native I run this comand to test the link
npx uri-scheme open exp://192.168.1.101:19000/--/home/this_is_a_test --ios
Docs here:
https://reactnavigation.org/docs/deep-linking/#test-deep-linking-on-ios
As you may see I'm passing "this_is_a_test" as a param to home link, but if I do console.log(route) in userStartSession component, I always get undefined in params
This is the response
Object {
"key": "userStartSession-CLSUaGx7QkCe8qlrvuvTf",
"name": "userStartSession",
"params": undefined,
"state": Object {
"index": 0,
"key": "stack-cyZDx2Q4gdGaZ1dMr2V1Q",
"routeNames": Array [
"slide-home",
"walkthrough-slides",
"sign-up-invitation",
"login-form",
"recover-account-password",
"sign-up-form",
"sign-up-payment-form",
"sign-up-payment-success",
],
"routes": Array [
Object {
"key": "sign-up-invitation-SSf39_Tj79VFv34ydou3N",
"name": "sign-up-invitation",
"params": undefined,
},
],
"stale": false,
"type": "stack",
},
}

Navigate when clicking on a button in react native

I could successfully implemented the stack and tab navigation in my project.
import React from "react";
import { StackNavigator } from "react-navigation";
import { TabNavFooter } from "./TabNavFooter";
import { SIGNIN_KEY, SIGNUP_KEY } from "../config/routeKeys";
import {
SignupScreen,
SigninScreen,
MainFeedScreen,
ProfilePageScreen,
CommentScreen
} from "../screens";
export const Routes = StackNavigator({
signin: { screen: SigninScreen },
comments: { screen: CommentScreen },
mainfeed: { screen: TabNavFooter },
signup: { screen: SignupScreen },
profilePage: { screen: ProfilePageScreen }
});
Now I want to navigate when I click on the comment button. My routes are in router/index.js file. How can I use this to navigate when I'm on another component? I tried this, but it didn't work.
export default class Post extends Component {
constructor(props) {
super(props);
}
commentPressedHandler = () => {
this.props.navigation('comments');
};
you should add navigate like this this.props.navigation.navigate('Your Screen')
so try to change your code like :
commentPressedHandler = () => {
this.props.navigation.navigate('comments');
};
instead of :
commentPressedHandler = () => {
this.props.navigation('comments');
};
Here is a solution using #react-navigation/native and #react-navigation/native-stack from the react native navigation documentation.
https://reactnative.dev/docs/navigation
Build React-Native route controller
import * as React from 'react';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import Login from "./Login";
import Home from "./Home";
const Stack = createNativeStackNavigator();
const NavigationStack = () => {
return (
<NavigationContainer>
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen
name="Login"
component={Login}
/>
<Stack.Screen name="Home" component={CreateAccount} />
</Stack.Navigator>
</NavigationContainer>
);
};
export default NavigationStack;
Get the navigation component injected into your view ex:
const Login = ({ navigation }) => {
Implement a function for your button that navigates to a different view.
Login = async event => {
console.log("Login")
// Do Login Stuff
// Navigate to Home View by using navigation component
navigation.navigate("Home");
}