Here is my code:
const Stack1 = createStackNavigator(
{
Aone: AoneScreen,
Atwo: AtwoScreen,
}
const Stack2 = createStackNavigator(
{
Bone:BoneScreen,
Btwo: BtwoScreen,
}
const Stack3 = createStackNavigator(
{
Cone:ConeScreen,
Ctwo: CtwoScreen,
}
const TabNavigator = createBottomTabNavigator(
Stack1,
Stack2,
Stack3
)
When I'm in a stack like "AoneScreen" and I move into another stack, say "CtwoScreen", and then press "back" button, instead of moving back to the first stack AoneScreen, it moves to the top of the second stack (ConeScreen) As it should! But that's not what I desire. what I want is go back to the original stack as the back button is pressed (in this case "AoneScreen" ) and I was wondering if that's possible.
You can implement custom behaviour for back button on screen "CtwoScreen" with BackHandler from React Native, it only depends on how dynamic this needs to be.
From documentation:
import { BackHandler } from "react-native";
componentDidMount() {
this.backHandler = BackHandler.addEventListener('hardwareBackPress',this.handleBackPress);
}
componentWillUnmount() {
this.backHandler.remove()
}
handleBackPress = () => {
// add some kind of custom check if you want this behaviour only sometimes, nav params are a good way
if (this.props.navigation.state.params.isFromAOneScreen) {
this.props.navigation.setParams({ isFromAOneScreen: false });
this.props.navigation.popToTop(); // to remove COneScreen from stack, from transition
this.props.navigation.navigate("AOneScreen");
return true;
}
return false;
}
Related
Let's say our current stack is ScreenA -> ScreenB. How can I return to ScreenA with a result (data) from ScreenB? Similar to android startActivityForResult and onActivityResult
Edit: The goal is to return to ScreenA from ScreenB with a result, keeping ScreenA state if possible.
you can just navigate to ScreenA with parameters.
In your ScreenB, add the following to navigate:
const ScreenB = ({navigation}) =>{
...
navigation.navigate("ScreenA",{data});
...
In your ScreenA, you listen for it.
const ScreenA = ({navigation, route}) => {
useEffect(()=>{
//this block listens for data change, then act on it.
},[route.data]);
...
}
Please refer to this LINK.
Screen 1:
onPress=()=>{
this.props.navigation.navigate('Screen2', {
yourArray: this.state.yourArray, });
}
}
Screen 2:
constructor(props) {
super(props);
var params = props.navigation.state.params.yourArray;
}
In this example, we will input the value in the first screen and get it into the second screen.
The value (param) is passed as an object in the first screen to the navigation.navigate function as:
this.props.navigation.navigate('RouteName', { /* params go here */ })
The same value (param) is read in the second screen as:
this.props.navigation.getParam(paramName, defaultValue)
I have the following Stack Navigation:
const WelcomeStack = createStackNavigator({
UpdateProfileScreen: {
screen: UpdateProfileScreen,
},
SelectProfileScreen: {
screen: SelectProfileScreen,
},
PresentationModal: {
screen: PresentationModal,
}
}, {
initialRouteName: 'UpdateProfileScreen',
headerMode: 'none'
})
When a user is new, I show "UpdateProfileScreen" first, then I move to "SelectProfileSecreen" and then "PresentationModal".
If for some reason after "UpdateProfileScreen" user closes the app, next time they log in I will show "SelectProfileSecreen" and "PresentationModal". If they complete data, next time, they will only see the "PresentationModal"
Since I have set "UpdateProfileScreen" as initialRoute, it will always load first, even if it does not show.
So I was wondering if programatically I could change the initialRoute and/or if I do:
this.props.navigation.navigate("WelcomeStack")
I can add some logic in the same stack to handle the page to show?
I think your best option is using SwitchNavigator(RouteConfigs, SwitchNavigatorConfig) you can simply create it with createSwitchNavigator as it controls all the switching of navigators used mainly for authentication flow in most react native apps.
With that said, for your case i think its the most suitable way to achieve the desired behavior.
Please be careful if publishing an app using something along the lines of :
this.props.navigation.navigate("WelcomeStack")
as it can be a serious security vulnerability
docs: https://reactnavigation.org/docs/1.x/switch-navigator/
snack example: https://snack.expo.io/#react-navigation/auth-flow
I had a similar problem here how i resolved it:
Added SwitchNavigator like so:
let Navigation = createSwitchNavigator(
{
AuthLoading: AuthLoading,
LoginNavigator: LoginNavigator,
HomeNavTab: tabNavigator,
LoggedInChose: LoggedInChose
},
{
initialRouteName: "AuthLoading"
}
);
The AuthLoading stack is the first to load and decides where the app go next:
import React from "react";
import { connect } from "react-redux";
class AuthLoading extends React.Component {
constructor(props) {
super(props);
this.checkUserSession();
}
checkUserSession = () => {
const userData = this.props.userData;
if (userData) {
const residencesNumber = userData.nbreResidences;
if (residencesNumber == 1) {
this.props.navigation.navigate("HomeNavTab");
} else {
this.props.navigation.navigate("LoggedInChose");
}
} else {
this.props.navigation.navigate("LoginNavigator");
}
};
// Render any loading content that you like here
render() {
return (
null
);
}
}
const mapStateToProps = state => {
return {
userData: state.userData
};
};
export default connect(mapStateToProps)(AuthLoading);
Maybe you get an idea from this.
In react navigation, how can I replace the last two screens in a stack?
For example, if my current stack is screen1 -> screen2 -> screen3, when I call .replace('screen4') on screen3,
it becomes screen1 -> screen2 -> screen4
But I want a way so it becomes screen1 -> screen4
You'll need to use reset to do it:
import { CommonActions } from "#react-navigation/native";
// ...
navigation.dispatch(state => {
// Remove the last 2 routes from current list of routes
const routes = state.routes.slice(0, -2);
// Reset the state to the new state with updated list of routes
return CommonActions.reset({
...state,
index: routes.length - 1,
routes
});
});
TL;DR
Scroll down for solution
Long answer
I find #satya164 to be a bit confusing, because the provided piece of code will actually remove the last two screens and simply push you back, unless you go on to push another screen using navigation.push or navigation.navigate.
So the actual solution would be:
import {CommonActions, StackActions} from '#react-navigation/native';
// this removes last two screens
navigation.dispatch(state => {
const routes = state.routes.slice(0, -2);
return CommonActions.reset({
...state,
index: routes.length - 1,
routes,
});
// can also be replaced by:
// return StackActions.popToTop();
});
// and pushes a new one directly
navigation.navigate('YourNewScreen', { /* optional params */ });
But even then, at least on iOS the animation will go backwards, as if a screen is being popped (i.e: user going backwards), instead of being pushed, which isn't very intuitive, and looks a bit weird.
I found that if you want to navigate to a new stacked screen, but want to discard the rest of the stack. The best way to do that, is to navigate to the needed screen, and to discard the rest of the screens keeping the first and last one (the one being presented) after the navigation has happened. This way, yo get the desired behavior and there are no weird jumps or wrong navigations animations.
Another plus is that this way you don't care how many screens you got in between, you just take the first and last. You can then put it in a neat little generic hook:
Solution
import {CommonActions} from '#react-navigation/native';
// call this hook in the last screen of the stack (so `screen4`)
export const useResetNavigationStackEffect = (props, condition) => {
const {navigation} = props;
useEffect(() => {
if (condition) { // only reset if `true`
navigation.dispatch(state => {
const topScreen = state.routes[0];
const thisScreen = state.routes[state.routes.length - 1];
const routes = [topScreen, thisScreen];
return CommonActions.reset({
...state,
index: routes.length - 1,
routes,
});
});
}
}, [condition, navigation]);
};
how to pass parameters in pop method.
Requirement: There are two screens, screen 1 has two tabs like this: Address and Billing. There are two button on each tab layout. On click button go to screen 2 after functionality back to screen 1 but now which tab is active. If go to address tab so back to address tab same as billing tab.
Tell me how to do it?
You can pass callback while pushing screen like
Navigation.push(this.props.componentId, {
component: {
name: "Your.ScreenName",
options: {
callback:this.yourCallBackFun
}
}
});
Now while pop you can call that function like
this.props.callback();
Navigation.pop(this.props.componentId);
I think it will help you.
Screen A:
this.props.navigation.navigate('ScreenB', {func: this.func});
...
func = value => {
console.log(value);
}
Screen B:
this.props.navigation.getParam('func')();
You can call ScreenA function like this.
Screen1:
Write your navigation function and callback function in your first screen.
Pass callback function as a navigation parameter white pushing the screen.
const cbFunction = () => new Promise((resolve) => {
resolve();
});
const navigation = () => {
const { componentId } = this.props;
Navigation.push(componentId, {
component: {
name: `SCREEN_NAME`,
options: {
cbFunction: this.cbFunction
}
}
});
}
Screen2:
Write a function to go back to first screen. And call callback function from navigation parameter.
const goBack = async () => {
const { cbFunction, componentId } = this.props;
await cbFunction();
Navigation.pop(componentId);
}
I have a navigation like so:
Loading: SwitchNavigator {
Auth: Stacknavigator,
Main: StackNavigator,
Onboard: StackNavigator,
}
every one of StackNavigators has an initial route set. Under certain circumstances, I want to go from loading navigator to a specific route on onboard navigator. If I use just navigator.navigate('DesiredRoute'), it squeezes in also onboard's initialRoute, so the navstack looks like [InitialRoute, DesiredRoute] instead of [DesiredRoute]. How to get rid of the InitialRoute?
Code example:
// Loading.js
if (loadingComplete) {
if (!user) {
navigation.navigate('auth')
return
}
if (user && userData) {
navigation.navigate('Main')
return
}
if (onboardingCheckpointCleared) {
// this creates `['InitialRoute', 'DesiredRoute']` instead of `['DesiredRoute']`
navigation.navigate('DesiredRoute')
return
}
navigation.navigate('Onboard')
}
This is expected behavior, if you want only DesiredRoute to appear then you have to set that route in Loading as well like below,
Loading: SwitchNavigator {
Auth: Stacknavigator,
Main: StackNavigator,
Onboard: StackNavigator,
DesiredRoute: ScreenName
}
Writing like this will not create any clash, you are safe to write.
I assume you are navigating to DesiredRoute from outside the Onboard stack navigator
If you're outside the Onboard navigator, doing navigation.navigate('DesiredRoute') will trigger two actions: first, it will navigate to the Onboard stack navigator (so it will also activate the default sub screen of it InitialRoute like you call it) and then it will push the DesiredRoute. If you want to go to Onboard with only DesiredRoute on the stack, you can use the sub actions of the navigation actions like this:
navigation.navigate('Onboard', undefined, StackActions.replace('DesiredRoute'));
The third argument is an action that can be will be executed by the first argument navigator (if the first argument is not a screen but a navigator). Here it will navigate to the Onboard navigator and thus put the InitialRoute in the stack (automatically as it's the initialRoute of Onboard). However, the StackAction.replace('DesiredRoute') will be executed by the Onboard navigator and will replace InitialRoute with DesiredRoute.
See the official doc about the navigate: https://reactnavigation.org/docs/en/navigation-prop.html#navigate-link-to-other-screens
I ended up creating a custom router that strips out the initial route when navigating to my nested stack:
const MyStackNav = createStackNavigator({ ...routes })
const defaultGetStateForAction = MyStackNav.router.getStateForAction
const customGetStateForAction = (action, state) => {
const defaultNavState = defaultGetStateForAction(action, state)
// If we're pushing onto a stack that only has a defaulted initialRoute
if (
!!defaultNavState &&
!defaultNavState.routeName &&
defaultNavState.isTransitioning &&
defaultNavState.index === 1 &&
action.type === NavigationActions.NAVIGATE
) {
const newState = {
...defaultNavState,
index: 0, // Decrement index
routes: defaultNavState.routes.slice(1), // Remove initial route
}
return newState
}
return defaultNavState
}