I have this scenario where i have a StackNavigator nested in a TabNavigator nested in another StackNavigator.
const TabOneStack = StackNavigator({
ScreenA: { screen: ScreenA },
ScreenB: { screen: ScreenB }
});
const MainTabs = TabNavigator({
TabOne: { screen: TabOneStack },
TabTwo: { screen: TabTwoStack }
});
const Root = StackNavigator({
HomeScreen: { screen: HomeScreen },
MainTabs: { screen: MainTabs }
});
Everything works but i cant figure out how to navigate for example from ScreenA back to the Home screen in the root StackNavigator.
After the HomeScreen the User navigates directly to ScreenA.
The back button in the header in ScreenA works fine and brings me back to Home but need a way to have a button that brings me back to the HomeScreen.
this.props.navigation.goBack() does not work unfortunately.
i also tried
const backAction = NavigationActions.reset({
index: 0,
key: null,
actions: [
NavigationActions.navigate({ routeName: 'HomeScreen'})
]
});
this.props.navigation.dispatch(backAction));
but i get:
There is no route defined for key HomeScreen. Must be one of: 'ScreenA', 'ScreenB'.
What is the correct way to do this?
To traverse from child StackNavigator back to parent StackNavigator, do:
class ScreenA extends Component {
render() {
return (<Button onPress={() => {
this.props.navigation.dispatch({type: 'Navigation/BACK'});
}} title='ScreenA. back to Home' />);
}
}
this.props.navigation.dispatch() with 'Navigation/BACK' works exactly the same as the top-most back button.
It differs from goBack() by apply parent-child stack traversing, while goBack() does not.
Result:
And note #Jigar's answer is also correct, it's just a shorthand notation of mine. The key is to pass null argument into goBack(). It won't work without that.
this.props.navigation.goBack(null);
In the new react native version we use 'StackActions' for this case:
StackActions reference
the 'key' parameter in this case is the key to solve this problem. Usually we manage the stack like this:
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'ScreenA' })],
});
this.props.navigation.dispatch(resetAction);
but StackActions object has another option and its: 'key' - its an optional string or null - If set, the navigator with the given key will reset. If null, the root navigator will reset.
So in this case, to reset back to root and navigate to some screen inside it -> in your case from 'ScreenA' to 'HomeScreen' - you should write:
this.props.navigation.dispatch(
StackActions.reset({
index: 0,
key:null,
actions: [
NavigationActions.navigate({
routeName: 'HomeScreen'
})
]
})
);
Notice, in this new version we no longer use 'NavigationActions' to reset the stack, only 'StackActions'.
use this
this.props.navigation.goBack(null);
In v6 you can use something like this:
navigation.getParent()?.navigate('Home')
For me, it's should working fine by using this:
Change this:
this.props.navigation.dispatch(backAction));
to
this.navigator.dispatch(backAction);
Also in your render
render() {
return (
<Root
ref={(nav) => {
this.navigator = nav;
}}
/>
);
}
Related
I'm using React-navigation 6 on my React native application
I have two StackNavigator in a BottomStackNavigator with those screens and params on each stack :
export type BottomNavigationStack = {
Planner: undefined,
Profile: undefined,
}
export type PlannerNavigationStack = {
PlannerHome: undefined,
PlannerDetails: {
detail: { ... }
},
Profile: {
screen: string,
initial?: boolean,
params?: { [name: string]: string }
}, // We can go to ProfileNavigationStack from PlannerNavigationStack
}
export type PlannerNavDetailProps = StackScreenProps<PlannerNavigationStack, 'PlannerDetails'>
export type ProfileNavigationStack = {
ProfileHome: undefined,
ProfileAddThings: {
service: string
from: string,
},
Planner: {
screen: string,
initial?: boolean,
params?: { [name: string]: string }
}, // We can go to PlannerNavigationStack from ProfileNavigationStack
}
export type ProfileNavAddThingsProps = StackScreenProps<ProfileNavigationStack, 'ProfileAddThings'>
So i'm trying to navigate from PlannerStack to ProfileStack, when I'm in ProfileStack, I want to go back to PlannerStack.
I got no problem going to ProfileStack from PlannerStack like this :
navigation.navigate('Profile', {
screen: 'ProfileAddThings',
params: {
service: 'myservice',
from: 'myfrom',
},
initial: false,
})
So at that moment I'm on ProfileStack on screen ProfileAddThings.
if I read the documentation, the PlannerStack keep it's history, and if I click on the bottomTab button of Planner I can see the page is still PlannerDetails.
But, when I click on the back button of the screen ProfileAddThings, I'm going back to ProfileHome.
I tried to overide the backButton action of the screen ProfileAddThings with that code :
navigation.navigate('Planner', { screen: 'PlannerDetails' })
But I got the error : undefined is not an object (evaluating 'route.params.detail')
Detail is a parameter of PlannerDetails screen.
I really don't understand why this parameter is undefined because de PlannerStack history is still present.
Someone has already gone back from a nested navigation in react native ?
Thanks
You need to get the parent navigator.
You can do this by using the getParent() method from the current screen.
Will be something like this:
let parent = navigation.getParent(); // This will assing PlannerStack to the parent variable.
then you should call navigation method with the Route name
parent.navigate('PlannerDetails');
The route history, register the hole component from the screen (in your case PlannerStack) and in your PlannerStack the innitialRoute probably is ProfileHome, thats the reason you get there when using the back button.
I have 5 tabs on my Home Screen.
Home Tab
Search tab
AddPostTab
Notifications Tab
Profile Tab
AddPostTab is like this.
const AddPostTab = createStackNavigator({
AddPost: {
screen: AddPost,
},
ImageDescription: {
screen: ImageDescription
},
},
{
headerMode:'none',
mode:'modal'
}
);
When I go back from ImageDescription screen to Home Screen and then if I go to AddPostTab again, I'm directly going to the ImageDescription screen. But I want to be able to go to AddPost screen.
I've also tried
const resetAction = StackActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'AddPost' }),
],
});
this.props.navigation.dispatch(resetAction);
but this only takes me to the AddPost Screen. But if I use Home instead of AddPost, it doesn't works.
How can I reset my stack in my case so that I can go to Home screen?
These are completely different navigators. And reset can't be applied to TabNavigator, because it's not much sense to do it.
What you can do - is do something like this:
ImageDescription.js:
goToHome() =>
this.props.navigation.popToTop()
&& this.props.navigation.navigate('Home');
This will reset to root your current stack and then you will switch to Home tab
if you are have a single stack then use this on button click
navigation.dispatch(
CommonActions.reset({
index: 1,//the stack index
routes: [
{ name: 'Promote' },//to go to initial stack screen
],
})
)
for single stack you can also use the
navigation.popToTop()
but if you are wroking on tabs and then want to reset the tab then try this
<BottomTab.Screen
name="Post"
component={PostStack}
options={{
unmountOnBlur: true,// set this props in your tab screen options
title: 'Post',
tabBarIcon: focused => <TabBarIcon focused={focused} name="Post" />,
}}
/>
You can run this from the screen you want to reset the stack with any event.
This fixed the similar issue for me (React Navigation 5.x)
import {NavigationActions} from 'react-navigation';
navigation.reset(
[NavigationActions.navigate({routeName: 'Profile'})],
0,
);
Is there any way to configure react-navigation so that single screen can handle multiple links?
Each screen in StackNavigator can have an optional property path which enabled deep links, StackNavigator also accepts paths option that lets u override paths per specific screen but it's still one-to-one mapping.
Is there a way to declare unlimited amount of paths that should be handled by single screen?
You can use variables for unlimited number of paths like its shown in StackNavigator docs
Example from docs
StackNavigator({
// For each screen that you can navigate to, create a new entry like this:
Profile: {
// `ProfileScreen` is a React component that will be the main content of the screen.
screen: ProfileScreen,
// When `ProfileScreen` is loaded by the StackNavigator, it will be given a `navigation` prop.
// Optional: When deep linking or using react-navigation in a web app, this path is used:
path: 'people/:name',
// The action and route params are extracted from the path.
// Optional: Override the `navigationOptions` for the screen
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile'`,
}),
},
...MyOtherRoutes,
});
Update
You can create a custom route handler for more detailed control over paths shown here.
Example from docs
import { NavigationActions } from 'react-navigation'
const MyApp = StackNavigator({
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen },
}, {
initialRouteName: 'Home',
})
const previousGetActionForPathAndParams = MyApp.router.getActionForPathAndParams;
Object.assign(MyApp.router, {
getActionForPathAndParams(path, params) {
if (
path === 'my/custom/path' &&
params.magic === 'yes'
) {
// returns a profile navigate action for /my/custom/path?magic=yes
return NavigationActions.navigate({
routeName: 'Profile',
action: NavigationActions.navigate({
// This child action will get passed to the child router
// ProfileScreen.router.getStateForAction to get the child
// navigation state.
routeName: 'Friends',
}),
});
}
return previousGetActionForPathAndParams(path, params);
},
});
I'd like to know how the navigation supposed to happen, when redux stores the navigation state.
Short version:
If the redux store isn't in initial state, a screen is mounted without actually navigationg there.
Detailed description:
Now I'm able to navigate either by (1) using the navigation props given by the parent navigator (StackNavigator in my case) or by (2) dispatching an action.
1: this.props.navigation.navigate('main')
2: this.props.navigateToMainAction()
The reducer:
const INIT_STATE = Nav.router.getStateForAction(
NavigationActions.navigate({ routeName: 'login' })
);
const navReducer = (state = INIT_STATE, action) => {
const newState = Nav.router.getStateForAction(action, state);
return newState || state;
};
Nav structure:
const authStack = StackNavigator({
login: { screen: LoginScreen }
,forgottendPassword: { screen: LoginScreen }
}, {
initialRouteName: 'login'
,headerMode: 'none'
});
const homeDrawer = DrawerNavigator({
home: {
screen: HomeScreen
,navigationOptions: { drawerLockMode: 'locked-closed' }
}
}, {
drawerPosition: 'right'
,drawerWidth: 300
,contentComponent: props => <HomeDrawerMenu {...props} />
});
const mainStack = StackNavigator({
homeDrawer: {
screen: homeDrawer
,navigationOptions: ({ navigation }) => ({
header: <HomeMenu navigate={navigation.navigate} />
})
}
,partnerList: {
screen: PartnerListScreen
,navigationOptions: ({ navigation }) => ({
header: <PartnerListMenu navigation={navigation} />
})
}
}, {
initialRouteName: 'homeDrawer'
});
const Nav = StackNavigator({
auth: { screen: authStack }
,main: { screen: mainStack }
}, {
initialRouteName: 'auth',
headerMode: 'none',
});
When I try to navigate from auth/login to main and from there to main/partnerList it only works correctly, if the redux store is in initial state (after I cleared the async storage).
But when I reload the app, it mounts the main/partnerList component, without actually navigating there and thanks to that, the action which fetching the partnerList comp.'s data is also called.
Expected action order on load:
##INIT
Offline/STATUS_CHANGED
persist/REHYDRATE
check_token <- Check if the user is logged in (in this case he is)
check_token_commit
Navigation/NAVIGATE <- The user is forwarded to main, when he is logged in
Navigation/NAVIGATE <- The user goes to the partnerList component
fetch_partner_list <- Action for fetching the partner list
Offline/BUSY
fetch_partner_list_commit
Current action order on load:
##INIT
Offline/STATUS_CHANGED
persist/REHYDRATE
check_token <- Check if the user is logged in (in this case he is)
fetch_partner_list <- It's already fetching, but no navigate action was triggered yet
Offline/BUSY
check_token_commit
Navigation/NAVIGATE <- The user is forwarded to main, when he is logged in
Navigation/NAVIGATE <- The forwarding happens twice, for some reason
fetch_partner_list_commit
Navigation/NAVIGATE <- The user goes to the partnerList component
fetch_partner_list <- The only time it should be triggered
Offline/BUSY
fetch_partner_list_commit
The problem was coused by the inappropriate use of the react navigation.
I had to wrap the auth and main router and pass down in screenProps the root navigation object.
So, I have the following screens:
- ChatList
- NewRoom
- ChatRoom
Basically, I don't want to go back to Start a new chat from the just-created chat room ... but instead go directly into the chat rooms list. So far, I came up with the following:
const prevGetStateForActionChatStack = ChatStack.router.getStateForAction
ChatStack.router.getStateForAction = (action, state) => {
if (state && action.type === 'RemovePreviousScreen') {
const routes = state.routes.slice( 0, state.routes.length - 2 ).concat( state.routes.slice( -1 ) )
return {
...state,
routes,
index: routes.length - 1
}
}
return prevGetStateForActionChatStack(action, state)
}
And it theoretically works ... but there is a weird animation when removing the previous route after getting to the new room, as follows. Let me know if you guys have any solution to this issue ...
In react-navigation#3.0
import { StackActions, NavigationActions } from 'react-navigation';
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Profile' })],
});
this.props.navigation.dispatch(resetAction);
https://reactnavigation.org/docs/en/stack-actions.html#reset
In react-navigation#6.0
The reset action is replaced by replace.
import { StackActions } from '#react-navigation/native';
navigation.dispatch(
StackActions.replace('Profile', {user: 'jane',})
);
https://reactnavigation.org/docs/stack-actions/#replace
From your code it seems you are using react-navigation.
React-Navigation has a reset action that allows you to set the screen stack.
For example:
In your case,
Screen 1: Chat room
Screen 2: Chat list
If you want to remove the chatroom screen from your stack you need to write it as
import { NavigationActions } from 'react-navigation'
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'chatlist'})
]
})
this.props.navigation.dispatch(resetAction)
This will reset your stack to only one screen as initial screen that is chatlist.
actions array can have multiple routes and index defines the active route.
For further details refer the following link:
https://reactnavigation.org/docs/navigators/navigation-actions
Resetting the navigation stack for the home screen (React Navigation and React Native)
you should be able to use the following to change the animation:
export const doNotAnimateWhenGoingBack = () => ({
// NOTE https://github.com/react-community/react-navigation/issues/1865 to avoid back animation
screenInterpolator: sceneProps => {
if (Platform.isIos) {
// on ios the animation actually looks good! :D
return CardStackStyleInterpolator.forHorizontal(sceneProps);
}
if (
sceneProps.index === 0 &&
sceneProps.scene.route.routeName !== 'nameOfScreenYouWannaGoTo' &&
sceneProps.scenes.length > 2
)
return null;
return CardStackStyleInterpolator.forVertical(sceneProps);
},
});
and use it as follows:
const Stack = StackNavigator(
{
...screens...
},
{
transitionConfig: doNotAnimateWhenGoingBack,
}
);