Pop animation in React navigation - react-native

I'm using cardStyleInterpolator: CardStyleInterpolators.forHorizontalIOS for animation transition, the problem is animation for pop must be reverse of pushing but not the animation for push and pop is the same and screen transition is in the same way.
How can I change animation for pop so, transition animation is reverse of poping??
I try using it in stack that contains topbar but not works:
<Stack.Navigator
screenOptions={{
...TransitionPresets.SlideFromRightIOS,
}}
mode="card"
headerMode="screen"
>
<Stack.Screen
name="HOME"
component={HomeStack}
options={{
...TransitionPresets.SlideFromRightIOS,
}}
/>
</Stack.Navigator>
const Tab = createMaterialTopTabNavigator();
export default function HomeStack({
navigation,
route,
}: {
navigation: StackNavigationProp<any>;
route: any;
}) {
React.useLayoutEffect(() => {
navigation.setOptions({
headerShown: false,
});
}, [navigation, route]);
useFocusEffect(
React.useCallback(() => {
const onBackPress = () => {
BackHandler.exitApp();
return true;
};
BackHandler.addEventListener("hardwareBackPress", onBackPress);
return () => BackHandler.removeEventListener("hardwareBackPress", onBackPress);
}, []),
);
return (
<Tab.Navigator tabBar={props => <CentralTab {...props} />} tabBarPosition="bottom" lazy>
<Tab.Screen name="GROUPS" component={GroupsScreen} />
<Tab.Screen name="FEED" component={FeedStack} />
<Tab.Screen name="NEW" component={NewStack} />
</Tab.Navigator>
);
}

A way to achieve a Right to Left animation on navigation (and Left to Right when going back) is using the TransitionPresets.SlideFromRightIOS:
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator, TransitionPresets } from '#react-navigation/stack';
<NavigationContainer>
<MainStack.Navigator>
<MainStack.Screen name="ScreenA" component={ScreenA} />
<MainStack.Screen
name="ScreenB"
component={ScreenB}
options={{ ...TransitionPresets.SlideFromRightIOS }}
/>
</MainStack.Navigator>
</NavigationContainer>
You can see it working in this Snack. Works both on iOS and Android.

Related

React Native Drawer Navigation show headerLeft

I have a Drawer navigator which is inside a Stack navigator and I'd like to display a header. Currently I can display everything I want however because the header is defined at the Stack level the navigation inside the header is stack level not drawer level which is preventing me from opening the drawer.
Root stack
<Stack.Navigator
initialRouteName={"Splash"}
screenOptions={{}}
component={SplashScreen}
>
{ auth ?
<Stack.Screen name="Drawer" component={DrawerStack} options={({ navigation }) => ({
title: 'My App',
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
headerRight: () => (
<HeaderRight navigation={ navigation } />
),
headerTitleAlign: 'center',
headerTintColor: 'white',
headerStyle: {
backgroundColor: '#5742f5'
},
})} />
:
<Stack.Screen name="Auth" component={AuthStack} options={{
headerShown: false
}}/>
}
</Stack.Navigator>
Drawer stack
<Drawer.Navigator options={{
headerShown: true,
headerLeft: () => (
<HeaderLeft navigation={ navigation } />
),
}}>
<Drawer.Screen
name="Conversations"
options={{
title: props.title,
}}
component={ChatListScreen}
/>
<Drawer.Screen
name="ChatRoom"
options={{
drawerLabel: () => null,
title: null,
drawerIcon: () => null
}}
component={ChatRoomScreen}
/>
</Drawer.Navigator>
Note in the drawer navigator the line with headerLeft does nothing and is there to show where I attempted to put it thinking it would work. I did think it might be overlaying the stack one so I commented out the stack one and it didn't work.
HeaderLeft
export default function HeaderLeft ({ navigation }) {
const openMenu = () => {
navigation.toggleDrawer();
}
return (
<View style={styles.header}>
<Icon name='menu' onPress={openMenu} size={28} style={styles.icon} color="white"/>
</View>
)
}
My question is how can I refactor this to enable me to have the HeaderLeft component work to open the drawer. I will be adding more screens so ideally something I don't have to pass to each screen but if that is what works I am good with it too.
Options in DrawerStack not work. I modified it:
<Drawer.Navigator
screenOptions={{
headerLeft: () => <HeaderLeft />,
}}>
// ...
</Drawer.Navigator>
And a little change in HeaderLeft:
import { useNavigation } from '#react-navigation/native';
function HeaderLeft() {
const navigation = useNavigation();
const openMenu = () => {
navigation.toggleDrawer();
};
// render your Button
}
Demo: https://snack.expo.dev/#pqv2210/0d613b

When using both: Stack Navigator and BottomTab Navigator how do I hide the bottom tab when inside a Stack Navigation Screen?

I am having a Bottom Navigation with 2 Tabs so far (Home & Messages). When inside Messages I can press on a User to get navigated to the ChatScreen which is a Screen from the Stack Navigator. In that ChatScreen I want to hide the BottomTab. I know that it is possible to hide it by adding tabBarStyle: { display: "none" } to the <Tab.Screen /> but this doesn't work for the ChatScreen since it is not a Tab.Screen
import * as React from 'react';
import {View, Text} from 'react-native';
import {NavigationContainer, StackActions} from '#react-navigation/native';
import {createNativeStackNavigator} from '#react-navigation/native-stack';
import Home from './app/Screens/Home';
import CommentSection from './app/Screens/CommentSection';
import MessageScreen from './app/Screens/MessageScreen';
import ChatScreen from './app/Screens/ChatScreen';
import NavigationHeader from './app/global/headers/NavigationHeader';
import SendOffer from './app/Screens/SendOffer';
import {createBottomTabNavigator} from '#react-navigation/bottom-tabs';
import Icon from 'react-native-vector-icons/MaterialIcons';
import ChatScreenHeader from './app/Screens/ChatScreen/ChatScreenHeader';
const HomeStack = createNativeStackNavigator();
const HomeStackScreen = () => {
return (
<HomeStack.Navigator initialRouteName="Home">
<HomeStack.Screen
name="HomeScreen"
component={Home}
options={{
// header: AppBar,
headerShown: false,
}}
/>
<HomeStack.Screen
name="CommentSection"
component={CommentSection}
options={{
headerTitle: 'Home',
// animationTypeForReplace: 'push',
animation: 'slide_from_bottom',
}}
/>
<HomeStack.Screen
name="SendOffer"
component={SendOffer}
options={{
headerTitle: 'Home',
animation: 'slide_from_right',
}}
/>
<HomeStack.Screen
name="ChatScreen"
component={ChatScreen} //HIDE BottomTab INSIDE THIS COMPONENT
options={{
headerTitle: 'Messages',
animation: 'slide_from_right',
}}
/>
</HomeStack.Navigator>
);
};
const MessageStack = createNativeStackNavigator();
const MessageStackScreen = () => {
return (
<MessageStack.Navigator>
<MessageStack.Screen
name="MessageScreen"
component={MessageScreen}
options={{
headerTitle: 'Messages',
animation: 'slide_from_right',
}}
/>
<MessageStack.Screen
name="ChatScreen"
component={ChatScreen} //HIDE BottomTab INSIDE THIS COMPONENT
options={{
headerTitle: 'Messages',
headerShown: false,
animation: 'slide_from_right',
}}
/>
</MessageStack.Navigator>
);
};
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator initialRouteName="Messages">
<Tab.Screen
name="Home"
component={HomeStackScreen}
options={{
headerShown: false,
tabBarLabel: 'Home',
tabBarIcon: ({ color }) => (
<Icon name="home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Messages"
component={MessageStackScreen}
options={{
headerShown: false,
tabBarLabel: 'Messages',
tabBarIcon: ({ color }) => (
<Icon name="chat" color={color} size={26} />
),
// tabBarStyle: { display: "none" }
}}
/>
</Tab.Navigator>
</NavigationContainer>
);
}
Used solution by #Escaper from another question
useEffect(() => {
navigation.getParent()?.setOptions({ tabBarStyle: { display: "none" }});
return () => navigation.getParent()?.setOptions({ tabBarStyle: undefined });
}, [navigation]);
You could use createNavigationContainerRef to check the current route name via the getCurrentRoute() function inside the component that creates the BottomTabNavigator and then use tabBarStyle conditionally as you have suggested.
This could look as follows.
import { createNavigationContainerRef } from "#react-navigation/native"
const ref = createNavigationContainerRef();
const Tab = createBottomTabNavigator();
export default function App() {
const hide = "ChatScreen" === ref.current?.getCurrentRoute()?.name
return (
<NavigationContainer>
<Tab.Navigator initialRouteName="Messages">
<Tab.Screen
name="Home"
component={HomeStackScreen}
options={{
headerShown: false,
tabBarLabel: 'Home',
tabBarIcon: ({ color }) => (
<Icon name="home" color={color} size={26} />
),
}}
/>
<Tab.Screen
name="Messages"
component={MessageStackScreen}
options={{
headerShown: false,
tabBarLabel: 'Messages',
tabBarIcon: ({ color }) => (
<Icon name="chat" color={color} size={26} />
),
tabBarStyle: { display: hide ? "none" : "flex" }
}}
/>
</Tab.Navigator>
</NavigationContainer>
);
}

Using header buttons to navigate to another screen using react-navigaton

I have created a stack navigator with a header button which will be used to navigate to another screen, however I get this error when I try to goto another screen
TypeError: undefined is not an object (evaluating 'navigation.navigate')
And when I console.log(navigation) its undefined. But however when I create a button within a component & navigate to another screen it works. Its only the header buttons that are give me issues.
import {createStackNavigator} from '#react-navigation/stack';
import TabNav from './TabNavBar';
import TestingScreen from '../components/TestingScreen';
const Stack = createStackNavigator();
const NavTest = ({navigation}) => {
navigation.navigate('testingScreen');
};
const StackNavigatorContainer = () => {
return (
<Stack.Navigator>
<Stack.Screen
name={'tabNavigator'}
component={TabNav}
options={{
headerLeft: () => (
<Button title={'go to testing screen'} onPress={NavTest} />
),
}}
/>
<Stack.Screen name={'testingScreen'} component={PostInvoice} />
</Stack.Navigator>
);
};
export default StackNavigatorContainer;
I've also tried...
<Stack.Screen
name={'tabNavigator'}
component={TabNav}
options={{
headerLeft: ({navigation}) => (
<Button title={'go to testing screen'} onPress={() => navigation.navigate('testingScreen)} />
),
}}
/>
And
<Stack.Screen
name={'tabNavigator'}
component={TabNav}
options={{
headerLeft: () => (
<Button title={'go to testing screen'} onPress={({navigation}) => navigation.navigate('testingScreen)} />
),
}}
/>
``
To improve your understanding about my answer, I want to share my code that working well.
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
const AppStack = createStackNavigator();
<NavigationContainer>
<AppStack.Navigator>
<AppStack.Screen name="Home" component={Home}
options={({ navigation }) => {
return {
headerTitleAlign: 'center',
headerMode: 'screen',
headerRight: () => (
<Button
onPress={() =>
navigation.navigate('Options')}
title="Crete Group"
color="#00f"
/>
),
}
}}
/>
<AppStack.Screen name="Options" component{Groupoptions} />
</AppStack.Navigator>
</NavigationContainer>
I hope this code help you surely.

Is is possible to use navigation.toggleDrawer() in navigation options

In my navigation file , when I want to toggle drawer , get the following error :
TypeError: navigation.openDrawer is not a function.(In
'navigation.openDrawer()', 'navigation.openDrawer' is undefined)
This is my drawer:
const DrawerNavigator = () => {
return (
<Drawer.Navigator
initialRouteName="MYSHIFT"
>
<Drawer.Screen name="MYSHIFT" component={TopTabNavigator} />
</Drawer.Navigator>
)
}
And this is my container navigation :
const CareworkerNavigation = () => {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Login"
component={LoginScreen}
options={{ headerShown: false }} />
<Stack.Screen
name="Main"
options={({ navigation }) => {
return {
headerLeft: () => <Button title="LEFT BUTTON" onPress={() => {
navigation.toggleDrawer(); // <--- this line throws an error
}} />
}
}}
component={DrawerNavigator} />
</Stack.Navigator>
</NavigationContainer>
)
}
export default CareworkerNavigation
Why I can not use navigation.toggleDrawer() in navigation options?
Is is possible to remove this problem ?
If you check the React Navigation docs, "You will need to make the drawer navigator the parent of any navigator where the drawer should be rendered on top of its UI."
React Navigation docs reference
To answer your question : Yes , it is possible.
And here you have a working example:
import React from 'react'
import { Button, View } from 'react-native'
import { NavigationContainer } from '#react-navigation/native'
import { createDrawerNavigator } from '#react-navigation/drawer'
import { createStackNavigator } from '#react-navigation/stack'
const Feed = () => <View />
const Notifications = () => <View />
const Profile = () => <View />
const FeedStack = createStackNavigator()
const Home = ({ navigation }) => (
<FeedStack.Navigator>
<FeedStack.Screen
name="Feed"
component={Feed}
options={props => {
const { toggleDrawer } = props.navigation // <-- drawer's navigation (not from stack)
return {
headerLeft: () => <Button title="LEFT BUTTON" onPress={toggleDrawer} />
}
}}
/>
</FeedStack.Navigator>
)
const Drawer = createDrawerNavigator()
export default props => {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName="Feed">
<Drawer.Screen
name="Feed"
component={Home}
options={{ drawerLabel: 'Home' }}
/>
<Drawer.Screen
name="Notifications"
component={Notifications}
options={{ drawerLabel: 'Updates' }}
/>
<Drawer.Screen
name="Profile"
component={Profile}
options={{ drawerLabel: 'Profile' }}
/>
</Drawer.Navigator>
</NavigationContainer>
)
}
While constructing navigation at options, you refer to the navigation of the stack, what cant perform draw actions, try to construct it on header itself
<Stack.Screen
name="Main"
options={() => {
return {
headerLeft: (navigation) => <Button title="LEFT BUTTON" onPress={() => {
navigation.toggleDrawer(); // <--- this line throws an error
}} />
}
}}
component={DrawerNavigator} />
https://github.com/react-navigation/react-navigation/issues/55

How to pop on the top of stack once the tab is changed?

I have a tab bar with 4 tabs. Each of them have a stack navigator. I want to pop to Top once the tab is switched? I have an inkling it has to do with defaultNavigationOptions. Please suggest me something.
I used this:
import {StackActions} from 'react-navigation'
defaultNavigationOptions: ({ navigation }) => ({
tabBarOnPress: ({ navigation, defaultHandler }) => {
const { routeName } = navigation.state
navigation.dispatch(StackActions.popToTop());
defaultHandler()
},
})
const HomeStack = createStackNavigator();
function HomeStackScreen() {
return (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={HomeScreen} />
<HomeStack.Screen name="Details" component={DetailsScreen} />
</HomeStack.Navigator>
);
}
const SettingsStack = createStackNavigator();
function SettingsStackScreen() {
return (
<SettingsStack.Navigator>
<SettingsStack.Screen name="Settings" component={SettingsScreen} />
<SettingsStack.Screen name="Details" component={DetailsScreen} />
</SettingsStack.Navigator>
);
}
const Tab = createBottomTabNavigator();
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeStackScreen} />
<Tab.Screen
name="Settings"
component={SettingsStackScreen}
listeners={({ navigation, route }) => ({
tabPress: e => {
// navigation.popToTop();
navigation.navigate(route.name);
},
})}
/>
</Tab.Navigator>
</NavigationContainer>
);
}