Transition custom header component React Navigation - reactjs

I am creating a React Native app that's supposed to have a custom header for basically all screens, so I've made a Navbar component that I have set as a default header which works fine. Although when I navigate between screens in the app, the header is not transitioning as it is supposed to be. With a standard header, the old one fades out and the new one in when you navigate to another route in the stack, but mine just replaces itself automatically.
I believe the problem might revolve around that all screens have the "same" Navbar component, that is there is not a new one created for each screen, so when I navigate it just updates the props of the navbar and re-renders it.
Here is the navigator setup
App.js
...
const stackNavigator = createStackNavigator(
{
...
}, {
initialRouteName: "Home",
defaultNavigationOptions: ({ navigation }) => ({
header: (headerProps) => {
return <Navbar navigation={navigation} {...headerProps.scene.descriptor.options} /> //Will pass navigationOptions as props
},
animationEnabled: true
}),
navigationOptions: {
animationEnabled: true,
},
headerTransitionPreset: 'fade-in-place',
transitionConfig: () => {
return {
transitionSpec: {
duration: 2000, //Easier to see the navigation animation
}
}
}
);
The Navbar component is a regular React.Component, along with all other screen components if that makes any difference
Are there some kind of props I should take care of? I have search all over the web, especially on the React Navigation docs and API reference but have found no info.
Here are some examples of how it looks with my custom navbar and how it looks with the default header. Note that the default fades during the whole transition while my custom just switches view in an instance. The only change I made was comment out the header: ... part
Custom
https://imgur.com/PXvm7gA
Default
https://imgur.com/yk3jyr9

If I understood it correctly your header is not navigating with screen? If that is the case use headerMode='screen' According to react-navigation 5.*
<NavigationContainer>
<Stack.Navigator initialRouteName="List" headerMode='screen'>
<Stack.Screen name="Article"
component= { Article }/>
<Stack.Screen name="List"
component= { List }/>
</Stack.Navigator>
</NavigationContainer>
header mode set to screen navigate the header with screen which is common in android where as if you set it to float header will not navigate but changes its content which is common in ios. set it to none if you dont want header.

I'm on react-navigation#5.1.3 and the following works for me (taken from my code):
<Stack.Navigator>
<Stack.Screen
name={"route1"}
component={Route1View}
options={{title: "route 1 title"}}
/>
<Stack.Screen
name={"route2}
component={Route2View}
options={({navigation, route}) =>({
title: "route 2 title",
header: (props) => <CustomHeader {...props} />
})}
/>
</Stack.Navigator>
If you start with route 1, the default header is shown. When you push route 2, the custom header will fade in. When you leave, the default will show once again.

You can specify any component for left and right in the header in the screen configuration, you can have something like this:
createStackNavigator({
home: {
screen: (props) => (
<View style={{flex: 1}}>
</View>
),
navigationOptions: () => ({
title: `Title`,
headerRight: (
<React.Fragment>
<Button title={'First'} />
<Button title={'Second'} />
</React.Fragment>
)
})
},
});

Related

React Navigation routes disappear from history on navigate

I have a screen in my app (CameraScreen) that is both in my Tab Navigator and Stack Navigator shown below (there are some more Tab Screens and Stack Screens that I removed for simplicity):
const TabNavigator = () => {
return (
<Tab.Navigator>
<Tab.Screen
name="Camera"
component={CameraScreen}
/>
</Tab.Navigator>
);
};
const Navigation = () => {
return (
<NavigationContainer theme={Theme}>
<Stack.Navigator headerMode="none">
<Stack.Screen name="Camera" component={TabNavigator} />
<Stack.Screen name="Product" component={ProductScreen} />
<Stack.Screen name="CameraStack" component={CameraScreen} />
</Stack.Navigator>
</NavigationContainer>
);
};
Now if I navigate to ProductScreen, then navigate to CameraStack from there and check the navigation state I notice that ProductScreen is nowhere to be found in the routes which I checked using navigation.getState().routes. Thus if I try and use navigation.goBack() it does not go back to the ProductScreen (which would be the expected behaviour).
When I check the routes in ProductScreen, ProductScreen shows up as the last route, however this disappears when I navigate to CameraStack.
I have a hunch that this has to do with the fact that CameraScreen is in both the Tab Navigator and Stack Navigator so for some reason the navigation prop passed to Camera is the Tab Navigator.
For reference my CameraScreen (simplified):
const CameraScreen = ({ navigation, route }) => {
// this doesn't include ProductScreen even if I navigate to CameraStack from the ProductScreen
console.log(navigation.getState().routes);
return (
<View></View>
);
};
and ProductScreen (simplified):
const ProductScreen = ({ navigation }) => {
return (
<View>
<TouchableOpacity
onPress={() => navigation.navigate("CameraStack")}
>
</TouchableOpacity>
</View>
);
};
One idea I can think of to resolve this issue is to manually pass a navigation parameter from ProductScreen but I'm wondering if there is a better way to handle this issue.
I realized this had nothing to do with the fact that I had this screen in both the Tab Navigator and Stack Navigator but was occurring because I was navigating to the same screen using navigation.navigate instead of navigation.push.
The former navigates to the previous location in your stack if you navigate to a screen you've already visited before, but the latter pushes a new route to the navigation state even if it's a screen you've already visited.
Handle backBehavior prop in your Navigator
<Tab.Navigator backBehavior='history'>
<Screen ... />
<Screen ... />
<Tab.Navigator/>
https://reactnavigation.org/docs/bottom-tab-navigator/#backbehavior

React Native - Navigation between nested screens

I have read the documentation regarding navigation between nested screens for react navigation version 5. However, I still keep running into an issue
Below is the code for my App.js
export default function App() {
return (
<NavigationContainer>
<Provider store={store}>
<Stack.Navigator initialRouteName="Login"
headerMode={'none'}>
<Stack.Screen name="Login" component={Login}></Stack.Screen>
<Stack.Screen name="BottomTabs" component={BottomTabs}></Stack.Screen>
</Stack.Navigator>
</Provider>
</NavigationContainer>
);
}
Below is the code for the "BottomTabs" component: -
export default function BottomTabs() {
return (
<Tab.Navigator initialRouteName="Profile">
<Tab.Screen name="Profile"component={Profile}></Tab.Screen>
<Tab.Screen name="Gobble" component={GobbleNavigator}></Tab.Screen>
<Tab.Screen name="Matches" component={Matches}></Tab.Screen>
<Tab.Screen name="Chats" component={ChatRoom}></Tab.Screen>
</Tab.Navigator>
)
}
In the "Profile" screen, I have a sign out button which, when clicked, executes a function where I call props.navigation.navigate('BottomTabs', {screen: 'Login'}) to go to the login page.
I've tried other things too such as navigation.navigate('BottomTabs', {screen: 'App', params: {screen: 'Login'}) and navigation.navigate('Login')
However, when I click the button, nothing happens.
What am I getting wrong here?
There should be a name associated with each screen.
Assuming the Login screen is known as 'Login', then if you want to click a button to navigate to the Login screen, you should be able to do it as:
<Button title="Signout"
onPress={() =>
this.props.navigation.navigate('Login') }
/>
However, if you are doing a sign-out operation, you should first say removing your current logged in data --- before you get navigated to the login screen.

React Native Navigation override goBack() of header back button in Stack Navigator

I want to customize the behavior of the default back button in stack navigator locally to one screen.
In the details assuming that on the stack there are screen1|screen2, I want to pass some props from screen2 to screen1 once the button is pressed.
I spent a lot of time reading React navigation docs, searching on internet and coding but I am not able to do this.
FROM DOCS
It's possible that in some circumstances that you want to customize the back button more than you can through the options mentioned above, in which case you can set the headerLeft option to a React Element that will be rendered
I know that the issue concerns the goBack() function of the headerRight component.
I want to override the default function goBack() related to the headerLeft back button with something like navigation.navigate("previousScreen",{{..props}}).
And ( this is very important!! ) I want to use this behavior locally to a specific screen, so not globally.
I have tried with something like this but doesn't works.
export default function App(){
return(
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="FirstScreen" component={FirstScreen}/>
<Stack.Screen name="SecondScreen" component={SecondScreen} options={{headerLeft: () => (
<HeaderBackButton
onPress={() =>navigation.navigate("FirstScreen",{//stuff//})}
title="Info"
color="#fff"
/>
),}}/>
</Stack.Navigator>
</NavigationContainer>
)}
If you are using react navigation v5, you can set the behaviour for a specific screen using :
import { HeaderBackButton } from '#react-navigation/stack';
...
options={{
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
navigation.navigate('screenName');
}}
/>
),
}}
...
You can also set as stack level using screenOptions={{}} instead.
"navigation" and "route" are also available on screen props.
options={({ navigation, route }) => ({headerLeft: ...})
For react-navigation v6, you can use setOptions inside a useEffect
import { HeaderBackButton } from '#react-navigation/elements';
...
const MyScreen = ({navigation}) => {
useEffect( () => {
navigation.setOptions({ headerShown: true,
headerLeft: (props) => (
<HeaderBackButton
{...props}
onPress={() => {
navigation.navigate('MyOtherScreen');
}}
/>
)
});
} );
}
When the component is mounted, register back button press listener
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.onBackButtonPressAndroid)
}
Then in the function, you can check and handle the action, ex:
onBackButtonPressAndroid = () => {
// Check if the current screen is focused or a subscreen is perhaps
if (this.props.navigation.isFocused()) {
if (someCondition) {
// Do a navigation to a custom screen, pass props here if needed
this.props.navigation.navigate('search')
return true
// Return true if you want to tell react navigation you've handled the back press
}
else if (this.state.offsetTop > 10) {
// Ex. for scrolling to top
this.scrollToTop()
this.setState({
offsetTop: 0,
})
return true
}
}
// Return false if you want to tell react navigation you didn't handle the back press
return false
}
Try passing navigationOptions to a specific screen :
export default function App(){
return(
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="FirstScreen" component={FirstScreen}/>
<Stack.Screen name="SecondScreen" component={SecondScreen}
navigationOptions: ({ navigation }) => ({ headerLeft: (<HeaderBackButton onPress={() => {}}/>)
})}}/>
</Stack.Navigator>
</NavigationContainer>
)}
Either you can specify it there on the navigation.
Or on the Second screen, try this :
class SecondScreen extends React.Component {
static navigationOptions = {
headerLeft: (
<Button
onPress={() => alert('This is a back button!')}
title="Title"
color="#fff"
/>
),
};
}

In React Navigation 5 in React Native, HeaderShown: false won't hide header

I've been experimenting in React Native and have noticed the expo init command now brings in a newer base codebase to start from. The issue I'm having is related the header not hiding when we use the headerShown prop in the options of the navigator component.
import * as React from "react";
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import TabBarIcon from "../components/TabBarIcon";
import HomeScreen from "../screens/HomeScreen";
import LinksScreen from "../screens/LinksScreen";
const BottomTab = createBottomTabNavigator();
const INITIAL_ROUTE_NAME = "Home";
export default function BottomTabNavigator({ navigation, route }) {
// navigation.setOptions({
// headerShown: false
// });
return (
<BottomTab.Navigator initialRouteName={INITIAL_ROUTE_NAME}>
<BottomTab.Screen
name="Home"
component={HomeScreen}
options={{
headerShown: false,
tabBarIcon: ({ focused }) => (
<TabBarIcon focused={focused} name="md-code-working" />
)
}}
/>
<BottomTab.Screen
name="Links"
component={LinksScreen}
options={{
headerShown: false,
tabBarIcon: ({ focused }) => (
<TabBarIcon focused={focused} name="md-book" />
)
}}
/>
</BottomTab.Navigator>
);
}
The code above is the different methods I've tried to apply the header shown. I have also tried doing it the older way by creating the static method for navigation options in each of the page components. Neither seems to work, and what's annoying is the docs have suggested applying it to the navigator is the way to use it in this version of react-navigation.
The Home page component looks like so:
export default function HomeScreen() {
return <View style={styles.container}></View>;
}
HomeScreen.navigationOptions = {
headerShown: false
};
And the Links page looks almost identical minus the render function.
For All Screen on this Stack
<LoggedStack.Navigator
screenOptions={{ headerShown: false}} >
<LoggedStack.Screen name='Dash' component={MyDrawer} />
<LoggedStack.Screen name="Login" component={Login} />
</LoggedStack.Navigator>
For Specific Screen
<Drawer.Screen name="Dept" component={DepartMentScreen} options={{ headerShown: false}} />
So as it turns out the new base code creates a stack navigator that references the screen with the bottom tab navigator applying the header shown prop to the stack navigator disabled the headers on the pages
<Stack.Navigator screenOptions={{ headerShown: false }}>

React-navigation: header does not show up when using bottom tab navigator

I am using react-navigation with my react native app. I have created a bottom tab navigator, and want to use the built in header on my screen. But the header is not showing up. There are no errors or warnings.
app.js:
const TabStack = createBottomTabNavigator({
Upload: {
screen: upload,
navigationOption: {
headerTitle: "Upload"
}
},
Profile: {
screen: profile,
navigationOption: {
headerTitle: "Profile"
}
}
});
const MainStack = createSwitchNavigator(
{
Home: { screen: TabStack }
},
{
initialRouteName: 'Home'
}
);
upload.js
class upload extends React.Component {
static navigationOptions = {
title: 'Upload'
};
constructor(props) {
super(props);
...
I know declaring navigationOptions in the components is probably not needed since it is already declared in app.js but this is just to show that neither approach works.
How do I fix this?
TabNavigator is not shipped with a Header. The most common case is to make your TabNavigator the root navigator, and make each tab a StackNavigator you would then get the header cause it's part of the StackNavigator by default.
The React Navigation docs also suggests adding a stack navigation for each tab.
The bottomTabNavigation screen does not have a header, but a normal StackNavigator does, so you can make your bottom tab open a normal StackNavigator.
Think of Instagram:
You open your home tab, and then enter a profile. When you go back, you still want to be in the home tab. So it's a Stack Navigation inside a Tab Navigation :)
const HomeStack = createStackNavigator();
function HomeStackScreen() {
return (
<HomeStack.Navigator initialRouteName="Feed">
<HomeStack.Screen name="Feed" component={FeedScreen} />
<HomeStack.Screen name="Profile" component={ProfileScreen} />
</HomeStack.Navigator>
);
}
const Tab = createBottomTabNavigator();
function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Home"component={HomeStackScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
Then the StackNavigator screen will add a header based on the name of the screen. You can also define a custom header title:
<HomeStack.Screen
name="Home"
component={HomeScreen}
options={{ headerTitle: "Custom title" }}
/>
MyTabs = tabNavigator
<Stack.Navigator>
<Stack.Screen name="MyAccount" component={MyTabs} />
</Stack.Navigator>
1) Use tabNavigator inside stack navigator as it comes with inbuilt header
functionality.
2) stack navigator do not have inbuilt header
it's work for me. try it as bellow
const TabStack = createBottomTabNavigator({
Upload: {
screen: createStackNavigator({Home: HomeScreen}),,
navigationOption: {
headerTitle: "Upload"
}
},
Profile: {
screen: profile,
navigationOption: {`enter code here`
headerTitle: "Profile"
}
}
});

Resources