React Navigation - Jumping between tabs - react-native

Hello guys I have 5 Containers in my tab bar and I would like to know if there is an option to return to the main screen if I select another tab container.
So I have the following
https://snack.expo.io/#react-navigation/stacks-in-tabs-v3
While I am in Home tab I click on Go to Details and then I switch to Settings tab. If I return to Home container I want to see the default screen which is HomeScreen
Any option?

You can redefine the tabBarOnPress of the navigationOptions of each StackNavigator like so:
const stacks = [
{ Home: HomeScreen, Details: DetailsScreen },
{ Settings: SettingsScreen, Details: DetailsScreen }
].reduce((stacksObj, stack) => {
const initialRoute = Object.keys(stack)[0];
stacksObj[initialRoute] = createStackNavigator(
stack,
{
navigationOptions: {
tabBarOnPress: ({ navigation }) => navigation.navigate({
routeName: initialRoute,
action: navigation.popToTop()
})
}
}
);
return stacksObj
}, {});
export default createAppContainer(createBottomTabNavigator(
stacks,
{ /* same defaultNavigationOptions as the snack example */ }
));

Cause your DetailScreen is nested in stack Home, you must navigate one more level to that screen
_navigateHome = () => {
const navigateAction = NavigationActions.navigate({
routeName: 'Home',
action: NavigationActions.navigate({ routeName: 'Home' }),
});
this.props.navigation.dispatch(navigateAction);
}

Looks the api has changed , the new way to achieve this is :
import { CommonActions } from '#react-navigation/native';
navigation.dispatch(
CommonActions.navigate({
name: 'Profile',
params: {
user: 'jane',
},
})
);

Related

Navigate to different stack - Can't perform update on unmounted component (no-op)

I'm using react-native-navigation v3 and I'm trying to go from a screen in one stack to another screen in a different stack.
When I trigger onDetailButtonPress from the LoggedInStack, I'm able to open the ReleaseDetail screen but the bottomTabNavigator disappears and I get an error: Can't perform a React state update on an unmounted component.
What is causing this issue here? I'm able to navigate from the HomeStack to the LoggedInStack but not the other way around.
HomeStack.js:
const HomeStack = createStackNavigator(
{
Home: {
screen: HomeScreen
},
Settings: {
screen: SettingsScreen
},
ReleaseDetail: {
screen: ReleaseInfoContainerScreen
},
SettingOnboard: {
screen: OnboardStack
}
},
{
headerMode: 'none'
}
)
LoggedInStack.js:
onDetailButtonPress={() => {
this.props.navigation.navigate(
NavigationActions.navigate({
routeName: 'HomeStackView',
params: {},
action: NavigationActions.navigate({
routeName: 'ReleaseDetail',
params: {
releaseId: selectedReleaseId
}
})
})
)
}}
export const LoggedInStack = createStackNavigator(
{
TabNavigator: {
screen: connectedLoggedInTabContainerView,
navigationOptions: {
header: null
}
},
FullScreenPlayer: {
screen: PlayerScreen,
navigationOptions: {
header: null
}
},
},
{
mode: 'modal',
headerMode: 'none'
}
)
Try to use:
onDetailButtonPress={() =>
this.props.navigation.navigate('ReleaseDetail', { releaseId: selectedReleaseId })
}

react-navigation: is this really the only way to access sibling and parent navigators?

So, I have a somewhat complicated nesting of navigators in my app (4 layers), and it is useful to be able to grab data from the params of each.
For instance, I have tabs, within tabs, that are inside a stack navigator. To access params from one of the screens therein I have do this:
const homeSiblingNavigator = props.navigation.dangerouslyGetParent().dangerouslyGetParent().getChildNavigation('Home');
First of all, calling dangerouslyGetParent() inspires absolutely no confidence.
Secondly, I feel like there should be a way, if you uniquely name every route, to find route by name.
I'm thinking of writing some weird recursive/loopy route finder. But before I do, I'm curious if there is a better way.
You can specify the path of the screen. And to get the parameters of the screen, getActionForPathAndParams can be used.
routing example
const AuthSwitch = createAppContainer(createStackNavigator({
AuthLoading: { screen: AuthLoadingScreen },
App: {
screen: AppStack,
path: '',
},
Auth: { screen: AuthStack },
}));
const AppStack = createStackNavigator({
Home: { screen: HomeScreen },
Friends: {
screen: FriendsScreen,
path: 'friends',
},
});
const FriendsScreen = createStackNavigator({
Overview: { screen: OverviewScreen },
Chat: {
screen: ChatScreen,
path: 'chat/:user',
},
});
getActionForPathAndParams Example
import { NavigationActions } from 'react-navigation'
const MyApp = createStackNavigator({
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);
},
});

React Navigation v3 Modal does not work with createBottomTabNavigator

React Navigation v3 Modal does not work with createBottomTabNavigator and not sure why. However, headerMode: 'none' seems to be working but mode: 'modal' is not showing up as modal.
const Addpicture = createStackNavigator(
{
Addpicture: {
screen: Addpicture
}
},
{
mode: 'modal', // This does NOT work
headerMode: 'none' // But this works
}
);
const Navigator = createBottomTabNavigator(
{
'My Interviews': {
screen: MyDatesStack
},
'Add Interview': {
screen: AddDateStack
},
'Companies': {
screen: CompaniesStack
}
}
);
export default createAppContainer(Navigator);
Indeed it doesn't work no matter what I tried.
I solved this problem by following steps below. Let's say you want to open modal when NewModal tab is pressed.
Set up your app container by including tabs stack & to be opened modal navigation stack:
const FinalTabsStack = createStackNavigator(
{
Home: TabNavigator,
NewModal: NewModalNavigator
}, {
mode: 'modal',
}
)
Create app container with that tabs stack per this guide
Inside the TabNavigator in the createBottomTabNavigator return null component for specific tab (NewModal) (to turn off navigation by react-navigator)
const TabNavigator = createBottomTabNavigator({
Feed: FeedNavigator,
Search: SearchNavigator,
NewModal: () => null,
Chat: ChatNavigator,
MyAccount: MyAccountNavigator,
}
defaultNavigationOptions: ({ navigation }) => ({
mode: 'modal',
header: null,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
if (routeName === 'NewModal') {
return <NewModalTab isFocused={focused} />;
}
},
}),
Handle click manually inside a custom tab component NewModalTab with TouchableWithoutFeedback & onPress. Inside NewModalTab component:
<TouchableWithoutFeedback onPress={this.onPress}>
<Your custom tab component here />
</TouchableWithoutFeedback>
Once you catch onPress dispatch redux event
onPress = () => {
this.props.dispatch({ type: 'NAVIGATION_NAVIGATE', payload: {
key: 'NewModalNavigator',
routeName: 'NewSelfieNavigator',
}})
}
Handle dispatched event using Navigation Service. I'm using redux-saga for it
NavigationService.navigate(action.payload);
A bit complicated but works.

Remove screen from stack navigator

I’ve observed Back button logic works seeing the sequence of screens in the stack. I’ve a Drawer navigator placed inside stack navigator.
On top of the stack I’ve Splash screen. On Dashboard when I press back button it takes me to splash screen.
How can I remove splash screen from stack after entering in the app, So when I press back button Dashboard it will EXIT the app instead of taking to SPLASH SCREEN.
/* #flow */
import React from "react";
import { Platform, Text } from "react-native";
import { Root } from "native-base";
import { StackNavigator, DrawerNavigator} from "react-navigation";
import Register from "./components/Register";
import Available from "./components/Available";
import Splash from "./components/splash/“;
import SideBar from "./components/sidebar";
import Discover from "./components/Discover/";
import Dashboard from "./components/Dashboard/";
import Contact from "./components/Contact"
const Drawer = DrawerNavigator(
{
Dashboard: { screen: Dashboard },
Discover: { screen: Discover },
Contact: { screen: Contact},
},
{
navigationOptions: {
gesturesEnabled: false,
},
initialRouteName: "Dashboard",
contentOptions: {
activeTintColor: "#e91e63"
},
drawerPosition: 'right',
contentComponent: props => <SideBar {...props} />
}
);
const AppNavigator = StackNavigator(
{
Splash: { screen: Splash },
Drawer: { screen: Drawer },
Available: { screen: Available },
Register: { screen: Register },
},
{
// initialRouteName: “Splash”,
headerMode: "none",
}
);
export default () =>
<Root>
<AppNavigator />
</Root>;
One solution would be to reset the stack inside the splash screen component and redirect the user to the screen that you prefer:
import { NavigationActions } from 'react-navigation'
const resetAction = NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Drawer'})
]
})
this.props.navigation.dispatch(resetAction)
For newer versions of react-navigation :
import { StackActions, NavigationActions } from 'react-navigation';
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Profile' })],
});
this.props.navigation.dispatch(resetAction);
Link to the official documentation
One of the simplest solutions would be, replace current screen with new screen so user won't be allowed to go back.
const SplashScreen: () => {
...
navigation.replace("LoginScreen"); // Move forward and Remove this screen from stack
see https://reactnavigation.org/docs/stack-actions/#replace
I solved it using this
code:
const prevGetStateForActionHomeStack = HomeStack.router.getStateForAction;
HomeStack.router = {
...HomeStack.router,
getStateForAction(action, state) {
if (state && action.type === 'ReplaceCurrentScreen') {
const routes = state.routes.slice(0, state.routes.length - 1);
routes.push(action);
return {
...state,
routes,
index: routes.length - 1,
};
}
return prevGetStateForActionHomeStack(action, state);
},
};
replaceScreen = () => {
const { locations, position } = this.props.navigation.state.params;
this.props.navigation.dispatch({
key: 'NearMeMap',
type: 'ReplaceCurrentScreen',
routeName: 'NearMeMap',
params: { locations, position },
});
};
Also if you need in-depth explanation of the code, watch this short video here
In react-navigation 5.x version. If you want to open new screen and remove previous stack screen, you can write: -
navigation.dispatch(StackActions.replace('screen_name', {params: {}}));
The replace action allows to replace a route in the navigation state. You can import it as:-
import { StackActions } from '#react-navigation/native';
If you want to perform other actions on stack navigation check out this link-
https://reactnavigation.org/docs/stack-actions
useEffect(() => {
setTimeout(() => {
navigation.reset({
index:0,
routes:[
{
name:"Welcome",
// params:{key:'param'},
},
],
});
}, 1000);},[]);
You can also trigger the this onPress like:
onPress={()=>navigation.reset({
index:0,
routes:[
{
name:"NameOfScreen",
// params:{key:'param'},
},
],
})}

Reset react-navigation Stack from Drawer

A lot of issues opened on official react-navigation github: https://github.com/react-community/react-navigation/issues but no one gives a real solution, including one post saying "look at this problem, a lot of issues": https://github.com/react-community/react-navigation/issues/691 without answer.
Basically: I have a DrawerNavigator with some nested StackNavigator.
export const MainNavigator = DrawerNavigator(
{
OrderNew: {
screen: OrderNew,
navigationOptions: {
drawerLabel: DrawerLabel('New Order')
}
},
Orders: {
screen: OrderNavigator, //a StackNavigator
navigationOptions: {
drawerLabel: DrawerLabel('Orders')
}
},
MenuNavigator: {
screen: MenuNavigator, //a StackNavigator
navigationOptions: {
drawerLabel: DrawerLabel('Menu')
}
},
//more screens...
{
initialRouteName: 'Orders',
drawerPosition: 'right',
contentComponent: props => <ScrollView style={{backgroundColor: 'rgba(227, 100, 29, .95)'}}><DrawerMenu state={props} /></ScrollView>,
}
);
So, I'm passing a contentComponent with my own menu component. My idea is to overwrite the onItemPress event of DrawerItems component and put my logic here.
What I want is: I would like to reset MenuNavigator (and other stack screens) every time users press that item. I just can't achieve this. As i said, I tried a lot of code. Actually, it's like this:
onItemPress={
router => {
const navigateAction = NavigationActions.navigate({
routeName: router.route.routeName,
});
this.props.state.navigation.dispatch(navigateAction);
}
}
The above code works, I can navigate through drawer items. Now, in my head, I just should pass an subaction resetting to the first item on navigator. So I tried:
onItemPress={
router => {
const navigateAction = NavigationActions.navigate({
routeName: router.route.routeName,
action: NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({routeName: router.route.routes[0].routeName}),
]
})
});
this.props.state.navigation.dispatch(navigateAction);
}
}
And explodes an error:
There is no route defined for key Menu.
Must be one of: 'Orders','OrderClient'
Summarizing: how could I reset StackNavigator when I do navigate to another DrawerNavigator's item?
You are missing key: null.
Navigation actions should be like this.
action: NavigationActions.reset({
index: 0,
key: null,
actions: [
NavigationActions.navigate({routeName: router.route.routes[0].routeName}),
]
})