Reset stack navigation when tab button is pressed in ReactNav 5 - react-native

I have 3 tabs and behind each tab is a stack navigation. I always want to reset the stack navigation when I click on another tab button.
Right now, when I go in Stack1 like A -> B -> C -> D
and I change to Tab2 and then change back to Tab1, I am again at Screen D.
I want to see Screen A again. I use React-Navigation-5
I would accept any answer that shows me a piece of code how to implement that.
My code looks like this:
App.js:
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Tab1" component={Stack1} />
<Tab.Screen name="Tab2" component={Stack2} />
<Tab.Screen name="Tab3" component={Stack3} />
</Tab.Navigator>
</NavigationContainer>
);
}
where as each of my stack navigations look like this:
function EventExploreStack({ navigation }) {
return (
<SettingsStack.Navigator initialRouteName="A">
<SettingsStack.Screen name="A" component={AScreen} />
<SettingsStack.Screen name="B" component={BScreen} />
<SettingsStack.Screen name="C" component={CScreen} />
<SettingsStack.Screen name="D" component={DScreen} />
<SettingsStack.Screen name="E" component={EScreen} />
</SettingsStack.Navigator>
);
}
export default EventExploreStack;
I am using React Navigation 5.

One option would be to use the reset action of the navigation. As you are having three stacks in three tabs you will need a custom tabbarbutton to do this which will reset the state of the given tab. The code for the button will be as below.
Here I've used Home and Settings as tabs, you will have to change them to your need.
const CustomButton = (props) => {
const navigation = useNavigation();
return (
<TouchableOpacity
{...props}
onPress={() => {
navigation.dispatch((state) => {
const newState = Object.assign(state);
const index = newState.routes.findIndex((x) => (x.name = 'Home'));
if (newState.routes[index].state) {
const { name, key } = newState.routes[index];
newState.routes[index] = { name, key };
}
return CommonActions.reset({
...newState,
});
});
navigation.navigate('Settings');
}}
/>
);
};
Which you can place in the tab screen as below
<Tab.Screen
name="Settings"
component={SettingsScreen}
options={{
tabBarButton: (props) => <CustomButton {...props} />,
}}
/>
You can tryout the sample here
https://snack.expo.io/#guruparan/bottomnavclick
Hope this helps :)

Take a look at the **createBottomTabNavigator** here https://reactnavigation.org/docs/bottom-tab-navigator/
Example
npm install #react-navigation/bottom-tabs
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}

Create a helper that reset the stacks of all Tabs that are not currently selected, and pass it to every tab.
Like this:
Helper:
import { StackActions } from '#react-navigation/native';
export const resetStacksOnTabClicks = ({ navigation }: any) => ({
tabPress: (e: any) => {
const state = navigation.dangerouslyGetState();
if (state) {
const nonTargetTabs = state.routes.filter((r: any) => r.key !== e.target);
nonTargetTabs.forEach((tab: any) => {
if (tab?.state?.key) {
navigation.dispatch({
...StackActions.popToTop(),
target: tab?.state?.key,
});
}
});
}
},
});
Then pass that helper to every Tab in the listeners prop like this:
<Tabs.Screen
name="TabName"
component={YourComponent}
listeners={resetStacksOnTabClicks}
/>

Related

React Native navigation 6 (Expo) - how can I toggle the drawer tab from the header of my Tabs navigation?

Hi I have react native (using expo) navigation 6. I am trying to have Tabs (Bottom) navigation and drawer navigation. I want to have the drawer nav hamburger menu inside the header. As far as I can see its being done having the following
headerLeft: (props) => {
return <Button
title="yes"
onPress={() => navigation.toggleDrawer() } />
}
either inside screenOptions or options (of the Tabs navigation). I kept it in screenOptions as I want it to be visible on all screens. However whatever I tried so far I am always getting an err saying either
undefined is not an object (wenn I pass in an object to screenOption (please see full code below)
or
navigation.ToggleDrawer is not a function - when I pass a function to screenOptions (please see full code below). I cannot find any solution or understand what I am doing wrong. Any help would be great!
const Drawer = createDrawerNavigator();
const DrawerNavigator = () => {
return (
<Drawer.Navigator>
<Drawer.Screen name="Test" component={Test} />
<Drawer.Screen name="s" component={SearchScreen} />
</Drawer.Navigator>
)
}
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator
screenOptions={({ navigation }) => ({ //here I get "undefined is not an object"
headerLeft: (props) => {
return <Button
title="yes"
onPress={() => navigation.toggleDrawer() } />
}
})}
/*screenOptions={{ // here I would get "navigation.ToggleDrawer is not a function"
}}*/
>
<Tab.Screen name="Home" component={HomeScreen}/>
<Tab.Screen name="Tab1" component={Tab1} />
<Tab.Screen name="Tab2" component={Tab2} />
</Tab.Navigator>
);
}
const Navigator = () => {
return (
<NavigationContainer>
<MyTabs />
</NavigationContainer>
)
}
export default Navigator;

React Navigation : Open drawer when I click on bottom tab navigator

With React Navigation 5, I want to open Drawer when I click on bottom tab navigator (I use material bottom navigator).
I manage to create the bottom tabs buttons and click on them, the home page opens for both tabs (GymIndexScreen or FoodIndexScreen).
When I am on the home pages (GymIndexScreen or FoodIndexScreen), I can open the different Drawers with my fingers (GymDrawerNavigator and FoodDrawerNavigator ) : everything works fine.
Question :
I want the drawers to open / close (toggle) automatically when I click the bottom tabs buttons, without having to open them with my fingers.
App.js :
import { NavigationContainer } from '#react-navigation/native'
const App = () => {
return (
<NavigationContainer>
<BottomTabNavigator />
</NavigationContainer>
)
}
BottomTabNavigator.js :
import { createMaterialBottomTabNavigator } from '#react-navigation/material-bottom-tabs'
const Tab = createMaterialBottomTabNavigator()
const BottomTabNavigator = (props) => {
return (
<Tab.Navigator>
<Tab.Screen
name="Gym"
component={GymDrawerNavigator}
options={{
tabBarLabel: "Musculation",
)}
/>
<Tab.Screen
name="Food"
component={FoodDrawerNavigator}
options={{
tabBarLabel: "Alimentation",
)}
/>
</Tab.Navigator>
)
}
GymDrawerNavigator.js :
import { createDrawerNavigator } from '#react-navigation/drawer'
const Drawer = createDrawerNavigator()
const GymDrawerNavigator = () => {
return (
<Drawer.Navigator>
<Drawer.Screen
name="Gym"
component={GymStackNavigator}
/>
</Drawer.Navigator>
)
}
GymStackNavigator.js :
import { createStackNavigator } from '#react-navigation/stack'
const Stack = createStackNavigator()
const GymStackNavigator = () => {
return (
<Stack.Navigator initialRouteName="GymIndex">
<Stack.Screen
name="GymIndex"
component={GymIndexScreen}
}}
/>
<Stack.Screen
name="GymExerciseIndex"
component={GymExerciseIndexScreen}
}}
/>
... list of screens
If I understood your problem correctly you want to open the drawer automatically when you navigate to the screen?
Add this to the screen components you wish to open the drawer when navigated to.
import {useEffect} from 'react'
...
useEffect(()=>{
navigation.addListener('focus', () => {
// when screen is focused (navigated to)
navigation.openDrawer();
});
},[navigation])``
This answer helped me.
Just use the listeners prop to preventDefault behaviour and then open the drawer.
<Tabs.Screen
name={"More"}
listeners={({ navigation }) => ({
tabPress: e => {
e.preventDefault();
navigation.openDrawer();
}
})}
component={Home}
/>

How to hide some pages from Drawer Navigation but still be able to navigate to them - React Native?

I want to hide some pages from the Drawer
I want to hide some pages from the Drawer (for example hide the SignUpPage and SuccessPage), how can I do it ?
i also tried to make an anonymous function in the DrawerLabel [ ()=> null ] but it is still not a good solution because even tho it shows me an empty label, yet when i click on it , it navigates me to the page that i wanted to hide.
Please help
and thanks for all the helpers :)
import { createDrawerNavigator } from '#react-navigation/drawer';
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="WelcomePage">
//...all the pages
<Drawer.Screen
name="HomePage"
component={HomePage}
options={{ drawerLabel: 'Home Page' }}
/>
<Drawer.Screen
name="SignUpPage"
component={SignUpPage}
options={{ drawerLabel: 'SignUp Page' }}
/>
<Drawer.Screen
name="SuccessPage"
component={SuccessPage}
options={{ drawerLabel: 'SuccessPage' }}
/>
</Drawer.Navigator>
);
}
const Stack = createStackNavigator();
export default function App() {
return (
< NavigationContainer >
<DrawerNavigator>
<Stack.Navigator initialRouterName="WelcomePage">
<Stack.Screen name="WelcomePage" component={WelcomePage} />
>
<Stack.Screen name="SuccessPage" component={SuccessPage} />
<Stack.Screen name="HomePage" component={HomePage} />
</Stack.Navigator>
</DrawerNavigator>
</NavigationContainer >
);
}
You have differents options
I guess you want to hide that options when your user is signed or not. With v5 you can do the code below. The another option is the same but playing with custom content that is a bit complex also I give you the docs if you want the complex solution https://reactnavigation.org/docs/drawer-navigator.
DrawerNavigator
const Drawer = createDrawerNavigator();
function DrawerNavigator() {
return (
<Drawer.Navigator initialRouteName="WelcomePage">
//...all the pages
<Drawer.Screen
name="HomePage"
component={HomePage}
options={{ drawerLabel: 'Home Page' }}
/>
</Drawer.Navigator>
);
}
AuthNavigator
const Stack = createStackNavigator<AuthParamList>();
export const AuthNavigator = () => {
return (
<Stack.Navigator headerMode='none'>
<Stack.Screen name='SignUpPage' component={SignUpPage}></Stack.Screen>
<Stack.Screen name='SuccessPage' component={SuccessPage}></Stack.Screen>
</Stack.Navigator>
);
};
IsAuthScreen, I use firebase + redux so here you need to put your login logic
const IsAuth: React.FC<RoutesProps> = (props) => {
const { eva, ...rest } = props;
const dispatch = useDispatch();
const onAuthStateChanged = (currentUser: any) => {
console.log("onAuthStateChanged -> currentUser", currentUser)
if (!currentUser) {
dispatch(new authActions.DidTryLogin());
} else {
if (!currentUser.emailVerified) {
dispatch(new authActions.DidTryLogin());
} else {
dispatch(new authActions.SigninSuccess(currentUser));
dispatch(new settingsActions.GetProfile(currentUser.uid));
}
}
};
useEffect(() => {
const subscriber = firebase.auth().onAuthStateChanged(onAuthStateChanged);
return () => {
subscriber();
}; // unsubscribe on unmount
}, [dispatch]);
return (<View >
<LoadingIndicator size='large' /> // Here put a loading component
</View>);
};
App component, I use redux for check if my user is logged so here you need to put your own logic
const isAuth = useSelector(selectAuthUser);
const didTryAutoLogin = useSelector(selectAuthDidTryLogin);
return (
<NavigationContainer>
{isAuth && <DrawerNavigator />}
{!isAuth && didTryAutoLogin && && <AuthNavigator />}
{!isAuth && !didTryAutoLogin && <IsAuthScreen />}
</NavigationContainer>);
So when you logout you don't need to navigate to SignInScreen (this will be a problem if you think about it because if you want to do that you can back to protected screens with the back button or gesture). You only need to update the state and the correct navigator will be in place and you can put the default screen to show. You can achieve this with redux or react context.

How to reset tab stack when you come back to a tab from another tab react navigation v5

I have 3 tabs and each tab contains a set of stack navigators.
Home Stack
const HomeNavigator = createStackNavigator();
const HomeStackNavigator = ({navigation, route}) => {
return (
<HomeNavigator.Navigator>
<HomeNavigator.Screen
name="Home"
component={Home}
/>
<HomeNavigator.Screen
name="Profile"
component={Profile}
/>
<HomeNavigator.Screen
name="Settings"
component={Settings}
/>
</HomeNavigator.Navigator>
);
};
Store Stack
const StoreNavigator = createStackNavigator();
const StoreStackNavigator = ({navigation, route}) => {
return (
<StoreNavigator.Navigator>
<StoreNavigator.Screen
name="OurStore"
component={Store}
/>
</StoreNavigator.Navigator>
);
};
Community Stack
const CommunityNavigator = createStackNavigator();
const CommunityStackNavigator = ({navigation, route}) => {
return (
<CommunityNavigator.Navigator>
<CommunityNavigator.Screen
name="Community"
component={Community}
/>
<CommunityNavigator.Screen
name="CommunityReply"
component={CommunityReply}
options={communityReplyOptions}
/>
<CommunityNavigator.Screen
name="AskCommunity"
component={AskCommunity}
/>
</CommunityNavigator.Navigator>
);
};
Tab Navigator
const MainNavigator = createBottomTabNavigator();
const MainTabNavigator = () => {
return (
<MainNavigator.Navigator
screenOptions={tabScreenOptions}
tabBarOptions={tabBarOptions}>
<MainNavigator.Screen
name="HomeTab"
component={HomeStackNavigator}
options={{tabBarLabel: 'Home'}}
/>
<MainNavigator.Screen
name="StoreTab"
component={StoreStackNavigator}
options={{tabBarLabel: 'Store'}}
/>
<MainNavigator.Screen
name="CommunityTab"
component={CommunityStackNavigator}
options={{tabBarLabel: 'Community'}}
/>
</MainNavigator.Navigator>
);
};
I navigated to CommunityReply Screen which is inside CommunityTab tab from HomeTab by clicking a button using the below approach
props.navigation.navigate('CommunityTab', {
screen: 'CommunityReply',
params: {postId: postId},
});
It's working fine, when I again come back to CommunityTab it will always be in CommunityReply Screen. How to reset tab stacks when you come back to a CommunityTab tab
React Navigation Versions
"#react-navigation/bottom-tabs": "^5.8.0"
"#react-navigation/native": "^5.7.3"
"#react-navigation/stack": "^5.9.0"
There is a property called unmountOnBlur designed for this purpose.
https://reactnavigation.org/docs/bottom-tab-navigator#unmountonblur
Changes to make in Tab Navigator :
const MainNavigator = createBottomTabNavigator();
const MainTabNavigator = () => {
return (
<MainNavigator.Navigator
- screenOptions={tabScreenOptions}
+ screenOptions={{...tabScreenOptions, unmountOnBlur: true }}
tabBarOptions={tabBarOptions}>
<MainNavigator.Screen
name="HomeTab"
component={HomeStackNavigator}
options={{tabBarLabel: 'Home'}}
/>
<MainNavigator.Screen
name="StoreTab"
component={StoreStackNavigator}
options={{tabBarLabel: 'Store'}}
/>
<MainNavigator.Screen
name="CommunityTab"
component={CommunityStackNavigator}
options={{tabBarLabel: 'Community'}}
/>
</MainNavigator.Navigator>
);
};
Summary:
unmountOnBlur: true will solve the problem mentioned.
If you don't want the CommunityReply screen to show when you navigate back to the CommunityTab you need to add an initial option within your navigate function.
props.navigation.navigate(
'CommunityTab',
{
screen: 'CommunityReply',
initial: false,
params: { postId: postId },
}
);
Explained here in the docs
I made a more generic approach to Jeison GuimarĂ£es solution (react navigation 6). This approach resets any stack navigation within a tab when the tab index changes. This also makes sure the active stack is not reset when a popover is shown.
<Tab.Screen
name="My tab"
listeners={resetTabStacksOnBlur}
/>
/**
* Resets tabs with stackNavigators to the first route when navigation to another tab
*/
const resetTabStacksOnBlur = ({navigation}) => ({
blur: () => {
const state = navigation.getState();
state.routes.forEach((route, tabIndex) => {
if (state?.index !== tabIndex && route.state?.index > 0) {
navigation.dispatch(StackActions.popToTop());
}
});
},
});
If the solution above not working for you, try this solution, its work for me.
import { CommonActions } from '#react-navigation/native';
<Tab.Screen listeners={({navigation,route})=>({
blur:()=>{
navigation.dispatch(
CommonActions.reset({
index:4,
routes:[{name:'Profile'}]
})
)
},
})} name="Profile"/>

How to create custom header options for each screen multi stack navigation react native?

I am looking for a way to set custom header options (styling, icons, enabling/disabling back button, left and right options for the header) for each of the screen in the stacks, while maintaining multi-stack architecture for my app in react native?
This is how my App.js looks right now, and I am willing to change it if need be.
const Stack = createStackNavigator();
const App = () => {
const ref = React.useRef();
const { getInitialState } = useLinking(ref, {
prefixes: ['FoodApp://'],
});
const AuthStack = createStackNavigator();
function AuthStackScreen() {
return (
<AuthStack.Navigator>
<AuthStack.Screen name="LogIn" component={LogIn} />
<AuthStack.Screen name="SignUp" component={SignUp} />
</AuthStack.Navigator>
);
}
const AppStack = createStackNavigator();
//I'd like to set different header options for each of the screen here
function AppStackScreen() {
return (
<AppStack.Navigator>
<AppStack.Screen name="MenuCategoryItems" component={MenuCategoryItems} />
<AppStack.Screen name="Delivery" component={Delivery} />
<AppStack.Screen name="Account" component={Account} />
<AppStack.Screen name="Notification" component={Notification} />
<AppStack.Screen name="Cart" component={Cart} />
</AppStack.Navigator>
);
}
//TODO: pass customized bar components
const Tab = createBottomTabNavigator();
//I'd like to set different header options for each of the screen here
function Tabs(){
return (
<Tab.Navigator tabBar={props => <BottomMenu {...props} />}>
<Tab.Screen name="Home" component={Home} />
<Tab.Screen name="Delivery" component={Delivery} />
<Tab.Screen name="Account" component={Account} />
<Tab.Screen name="Notification" component={Notification} />
<Tab.Screen name="Cart" component={Cart} />
</Tab.Navigator>
);
}
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={Tabs} />
<Stack.Screen name="AppStack" component={AppStackScreen} />
/*Should I place something else here so that I have access to both AppStack and Tabs navigations?*/
</Stack.Navigator>
</NavigationContainer>
);
};
export default App;
Not sure if this is what you need but here is how I customize some "borderColor" value for my header component.
You can pass any options you want via the setOptions method of the navigation component. It will be populated in the scene.descriptor.options parameter.
Below an example of how I am using it in my app.
the header options for my screen/navigator.
header: ({scene, previous, navigation}) => {
const {options} = scene.descriptor;
let borderColor = options.borderColor ?? 'yellow';
if (scene.route.name.startsWith('Test')) {
borderColor = 'blue';
}
return <HeaderBar options={options}
title={title}
previous={true}
navigation={navigation}
borderColor={borderColor}
/>;
}
And in any of the screens components I can use
useLayoutEffect(() => {
navigation.setOptions({
borderColor: 'orange'
})
}, [ navigation ])
Now, my <HeaderBar> component will receive a prop borderColor whose value is 'orange'.
export const HeaderBar = ({ options, title, previous, navigation, borderColor = 'yellow' }) => {
[...]
console.log(borderColor); // orange
}
As you can see, my HeaderBar component also receive the full options as a prop, therefore I could skip the borderColor prop and check the options inside the header component itself.
Like so
export const HeaderBar = ({ options, title, previous, navigation }) => {
const { borderColor } = options;
[...]
}
Hope it will help you if not too late.
Well. You could hide header default of react-navigation in your stack.
function AppStackScreen() {
return (
<AppStack.Navigator headerMode={'none'}>
<AppStack.Screen name="MenuCategoryItems" component={MenuCategoryItems} />
<AppStack.Screen name="Delivery" component={Delivery} />
<AppStack.Screen name="Account" component={Account} />
<AppStack.Screen name="Notification" component={Notification} />
<AppStack.Screen name="Cart" component={Cart} />
</AppStack.Navigator>
Then, you could custom header component and import it in each your screen.
Sorry because my EL. hope help U.