Reseting React nested Navigators within render - reactjs

Context: building a React Native mobile application. Along the bottom of the UI are four tabs. Three contain simple StackNavigators to preserve navigation state within that tab. The fourth screen is itself a tab navigator. The screen of each sub-tab is a StackNavigator to preserve their own navigation history while within the tab.
Outline:
The top level navigator:
BottomTabNavigator
|_Tab 1 (StackNavigator) (initial route)
|_Tab 2 (StackNavigator)
|_Tab 3 (StackNavigator)
|_Tab 4 (Tab4Container)
The sub-navigator:
Tab4Navigator (MaterialTopTabNavigator)
|_Subtab 1 (StackNavigator) (initial route)
|_Screen 4.1.1 (initial route)
|_Screen 4.1.2
|_Subtab 2 (StackNavigator) (initial route)
|_Screen 4.2.1 (initial route)
|_Screen 4.2.2
Tab 4 has a few components above the tabs themselves, so that's wrapped in a container. Essentially:
class Tab4Container extends React.router {
render() {
return(
<View>
<Header />
<Tab4Navigator navigation={this.props.navigation}>
</View>
)
}
}
Issue: The nested tab navigator doesn't reset its state when the user leaves Tab4 and comes back. First, the user clicks to Tab4, then Subtab 2, then Screen 4.2.2. Then the user clicks on Tab 2. Then the user returns to Tab 4. The current screen should be 4.1.1 but it is still 4.2.2.
Tab4Navigator is structured:
const Tab4Navigator = createMaterialTopTabNavigator(
{
subTab1: createStackNavigator(
{
screen_4_1_1: {
screen: Screen411Container
},
screen_4_1_2: {
screen: Screen412Container
},
},
{ initialRouteName: "screen_4_2_1" }
),
subTab2: createStackNavigator(
{
screen_4_2_1: {
screen: Screen421Container
},
screen_4_2_2: {
screen: Screen422Container
},
},
{ initialRouteName: "screen_4_2_1" }
),
},
{ initialRouteName: "subTab1" }
)
Question: How can I reset the StackNavigator when the Tab4Navigator is re-rendered?

You have to add tabBarOnPress event on Tab4Navigator and then reset stack . Check this GitHub link also
import { StackActions, NavigationActions } from 'react-navigation';
......
{ initialRouteName: "subTab1", tabBarOnPress: this.handleTabPress }
handleTabPress = ({ navigation }) => {
navigation.popToTop();
navigation.navigate(navigation.state.routeName);
}

Related

How to hide the navigation tab bar on a specific screen in react-navigation [duplicate]

I am using React Native and React Native Navigation to build out my application. Currently, I have three bottom tabs: Home, Upload Video and Messages. Upon selection of the Upload Video tab, I want to render the Upload Video component and hide the bottom tabs on just that screen, and display a header with 'Cancel' (takes them back to the HomeView) and 'Post' buttons (this has already been done). I've had an extremely difficult time hiding the tab bar on this specific screen.
I tried following the code here (How can I hide the bottom tab bar on a specific screen (react-navigation 3.x)); however, that ended up being unsuccessful and I was not able to hide the bottom tabs on any of the screens this way.
Currently, I have this as my bottom navigator:
const BottomTabNavigator = createBottomTabNavigator({
HomeView: {
screen: HomeView,
},
VideoView: {
screen: VideoSelectionView
},
Messages: {
screen: SearchView
}
});
Any insight would be extremely helpful, thanks.
I've traversed the internet like never before to find a solution for this problem as the provided solution by the docs did not work in the slightest.
I had the following navigational Set-Up:
Bottom Tabs
A (NativeStack)
1 (Screen)
2 (Screen)
3 (Screen)
B (NativeStack)
C (NativeStack)
I wanted to hide the bottom bar in Screen 1. What finally did the trick was the following snippet in the corresponding screen:
useEffect(() => {
navigation.getParent()?.setOptions({
tabBarStyle: {
display: "none"
}
});
return () => navigation.getParent()?.setOptions({
tabBarStyle: undefined
});
}, [navigation]);
The effect is run when the navigation prop updates and with that implicitly after the screen is being opened. With getParent() I get the bottom tabs navigator and can set the options with setOptions(...). To bring the bottom tabs back one has to manually set the options. I solved this by returning the method that resets the tabBarStyle in the call of useEffect(). This call is being made when it's time to clean-up, meaning that it will run as soon as the screen is being unmounted.
May this save same of you of the desperation I had to go through.
You need to specify for each TabBar screen or stack for which you need to hide tabbar,
const BottomTabNavigator = createBottomTabNavigator({
HomeView: {
screen: HomeView,
navigationOptions:()=>{
return {
tabBarVisible:false,
};
}
},
VideoView: {
screen: VideoSelectionView
},
Messages: {
screen: SearchView
}
});
on v5 you can modify options using a function and default arg navigation.:
<BottomTab.Screen
name="Explore"
component={Explore}
options={({ navigation }) => {
const { routes, index } = navigation.dangerouslyGetState();
const { state: exploreState } = routes[index];
let tabBarVisible = true;
if (exploreState) {
const { routes: exploreRoutes, index: exploreIndex } = exploreState;
const exploreActiveRoute = exploreRoutes[exploreIndex];
if (exploreActiveRoute.name === "RewardDetail") tabBarVisible = false;
}
return {
tabBarVisible,
title: "Explore",
tabBarLabel: "Explore",
tabBarIcon: ({ color, size }) => (
<AntDesign name="search1" color={color} size={size} />
),
};
}}
/>
see my answer: https://stackoverflow.com/a/64042879/5288560
Since react-navigation 5 is now being used, the above solution doesn't work anymore.
For React-Navigation 5, refer to this link.
In React Navigation V6 add display: none in options under tabBarStyle.
Add tabBarButton: () => null, to disable icon in Tab.
<Stack.Screen
name="Add Product"
component={AddProduct}
options={() => ({
tabBarStyle: {
display: "none",
},
tabBarButton: () => null,
})}
/>
Just setting the tabBarStyle to none doesn't work for me, I needed to use the property tabBarVisible too, if using hooks you can do something like that:
export function useHideBottomBar() {
const navigation = useNavigation();
useEffect(() => {
navigation.getParent()?.setOptions({ tabBarStyle: { display: 'none' }, tabBarVisible: false });
return () =>
navigation.getParent()?.setOptions({ tabBarStyle: undefined, tabBarVisible: undefined });
}, [navigation]);
}
In React navigation 5+ I used the following approach to hide a tab bar on a specific screen which was inside a stack navigator of a tab screen. In my tab navigator containing file I made a function, and then set the options property using the function which will trigger dynamically.
function getIsTabBarShown(route) {
const routeName = getFocusedRouteNameFromRoute(route) ?? routes.DASHBOARD;
switch (routeName) {
case routes.MEMBERDETAILS:
return false;
default:
return true;
}
}
This function will return false when user would go to MemberDetails Page which is inside MemberNavigator Stack.
<Tab.Screen
name="MemberTab"
component={MemberNavigator}
options={({ route }) => ({
title: 'Members',
tabBarVisible: getIsTabBarShown(route),
tabBarIcon: ({ color, size }) =>
<MaterialCommunityIcons name="account-group" color={color}
size={size} />
})} />
Here is the official docs to learn more click here.
Refer to the documentation by clicking here
After searching and trying a lot of methods I changed the top element View to Modal then hid bottombar, because modal can be upper bottom bar. It's not the best but still useful.
<View>
//code block
</View>
to->
<Modal animationType='fade' transparent={false} visible={true}>
/code block
</Modal>
In Version 6 this worked for me:
To hide the bottom tab navigator from the screen:
navigation.getParent()?.setOptions({tabBarStyle: {display: 'none'}});
To show the bottom tab navigator from the screen:
navigation.getParent()?.setOptions({tabBarStyle: {display: 'flex'}});
just on the Screen you want to hide the bar, set tabBarVisible: false.
<Tab.Screen
name="SignIn"
component={SignInScreen}
options={{
tabBarVisible: false, //like this
tabBarButton: (props) => null, //this is additional if you want to hide the tab element from the bottom nav
}}
/>

React Navigation navigate to specific tab

The example on React Navigation shows how you can jump to a tab, but it shows it in the case you are in the navigator the tabs are part of.
But how do you navigate to a specific tab from a screen which is part of a different navigator?
I have:
function MealsNavigator() {
const MealsStack = createStackNavigator();
return (
<MealsStack.Navigator>
<MealsStack.Screen
name="Meals"
component={MealsScreenTabs}
/>
</MealsStack.Navigator>
);
}
function MealsScreenTabs() {
const MealsTabs = createMaterialTopTabNavigator();
return (
<MealsTabs.Navigator>
<MealsTabs.Screen name="Upcoming" component={MealsUpcomingScreen} />
<MealsTabs.Screen name="Past" component={MealsPastScreen} />
</MealsTabs.Navigator>
);
}
I want to be able to navigate to Meals > Past with a button click from my Profile screen inside the Account navigator. How?
Got some help from the community on Discord. Here' how you do it:
navigation.navigate('Meals', {
screen: 'Meals',
params: {screen: 'Past'},
})
And to see it in action, have a look at a snack I created
in react navigation v5
navigation.navigate('Meals', {screen: 'Past'});

How to reset my stack in react-navigation?

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

react-navigation deep linking with multiple paths

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);
},
});

Navigate to parent of parent stack

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;
}}
/>
);
}

Resources