React-native : Dynamically update header title in stack navigator - react-native

I have made a custom component for header title(stack navigator)which shows user name along with some image.
On this page I have to edit the username and on success Update it in header as well
So my question is How to change/update title dynamically?

This can be done using the navigation props.
You can use this.props.navigation.state.params in a component to set a new property. Call:
navigation.setParams({ param: value })
See the documentation on headers for more detail.

For React Navigation version 1.x, 2.x, 3.x and 4.x, you can simply change the header by using the method shown in the code below, or the one in the original documentation: React Navigation - using params in the title
static navigationOptions = ({ navigation }) => {
const edit = navigation.getParam('edit', false);
if(edit){
return {
headerTitle: 'Edit Page',
};
}else{
return {
headerTitle: 'Normal Page',
};
}
};
For version 5.x and above, you may refer to the code below. Here are the links for the official documentation and example in expo.
import * as React from 'react';
import { View, Text, Button } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
function HomeScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button
title="Go to Profile"
onPress={() =>
navigation.navigate('Profile', { name: 'Custom profile header' })
}
/>
</View>
);
}
function ProfileScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Profile screen</Text>
<Button title="Go back" onPress={() => navigation.goBack()} />
</View>
);
}
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
options={{ title: 'My home' }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({ title: route.params.name })}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;

In React 5.0 or above you could do the following if you want to use a Class Component:
componentDidMount() {
this.props.navigation.setOptions({
title: `Your Updated Title`,
})
}

With React Navigation 5 and React Navigation 6 you can do this like this,
set Params in your component
props.navigation.navigate("ProductDetailScreen", {
productId: itemData.item.id,
productTitle: itemData.item.title,
});
And display it
<Stack.Screen
name="ProductDetailScreen"
component={ProductDetailScreen}
options={({ route }) => ({ title: route.params.productTitle })} // what
need to add
/>
or you can do this in your component with useEffect hooks
useEffect(() => {
props.navigation.setOptions({
title: productTitle,
});
}, [productTitle, props.navigation]);

navigation.setOptions({ title: 'Updated!' })
Reference.

the code that shows in the part below is for the version of react-navigation 2.x
you can try the following:
in my case I have the configuration of navigation in a file called, app-navigator.js
const ChatStackNavigator = createStackNavigator(
{
People: ListPeopleScreen, // People Screen,
Screen2: Screen2
},
{
initialRouteName: 'People'
navigationOptions: ({navigation}) => ({
header: <AppBar title={navigation.getParam('appBar', {title: ''}).title}/>
}),
cardStyle: {
backgroundColor: 'white'
}
}
);
The screen file would be as follows:
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {List, ListItem} from 'react-native-elements';
class ListPeopleScreen extends Component {
list = [
{
name: 'Amy Farha',
avatar_url: 'https://s3.amazonaws.com/uifaces/faces/twitter/ladylexy/128.jpg',
subtitle: 'Vice President'
},
{
name: 'Chris Jackson',
avatar_url: 'https://s3.amazonaws.com/uifaces/faces/twitter/adhamdannaway/128.jpg',
subtitle: 'Vice Chairman'
}
];
componentDidMount() {
this.props.navigation.setParams({
appBar: {
title: 'Clientes'
}
});
}
render() {
return <List
containerStyle={{marginBottom: 30}}
>
{
this.list.map((item) => (
<ListItem
roundAvatar
avatar={{uri: item.avatar_url}}
key={item.name}
title={item.name}
/>
))
}
</List>
};
}
export default connect(null)(ListPeopleScreen);
NOTE: in my case I am using redux, and the library of components react-native-elements

In version 3.x and 4.x this can be done using static navigationOptions function,
For class compenents,
class MyComponent extends Component {
static navigationOptions = ({navigation}) => {
return {
title: navigation.getParam('title', 'Fallback title');
};
}
updateHeader = () => {
// dynamically update header
navigation.setParams({title: 'MyComponent'});
}
render() {
// call updateHeader on click of any component
}
}
For functional components,
const MyComponent = (props) => {
const updateHeader = () => {
// dynamically update header
navigation.setParams({title: 'MyComponent'});
}
// call updateHeader on click of any component
}
MyComponent.navigationOptions = ({navigation}) => ({
title: navigation.getParam('title', 'Fallback title'),
})

for react navigation Version: 5.x
const ProductDetailScreen = props => {
const { productId } = props.route.params;
const { productTitle } = props.route.params;
props.navigation.setOptions({ title: productTitle });
return (
<View>
<Text>{productId}</Text>
</View>
);
};
for react navigation Version: 5.x

For version 4, this is work for me.
const HistoryScreen: NavigationStackScreenComponent<any, any> = (props) => {
const { navigation } = props;
useEffect(() => {
let device = props.navigation.getParam("device");
if(device) {
navigation.setParams({title: `History - ${device.name}`})
}
}, []);
... render view
HistoryScreen.navigationOptions = ({
navigationOptions,
navigation,
}) => ({
headerTitle: navigation.getParam("title") ? navigation.getParam("title") : "History"
});
}

function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="OrdersScreen"
component={OrdersScreen}
options={{ title: 'My Orders' }}
/>
</Stack.Navigator>
</NavigationContainer>
);
}

For React-Navigation v3, I used the following command to change the title of a stack:
Class component:
this.props.navigation.setParams({ title: res.title });
Function component:
props.navigation.setParams({ title: res.title });

If you use createStackNavigator you can do like this:
createStackNavigator({
// 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,
});
from the doc
call it like this:
navigation.navigate('Profile', {_id: item._id, name: item.screenName})}

Related

stack from NativeStackNavigator (nested in BottomTabNavigator) resets everytime the BottomTabNavigator changes tabs

Situation:
The react native app has a BottomTabNavigator (react-navigation/material-bottom-tabs) and one of the tabs has a NativeStackNavigator (react-navigation/native-stack).
BottomTabNavigator:
tab1
tab2
tab3:
NativeStackNavigator:
screen1
screen2
Problem:
When I press tab3 (from the BottomTabNavigator) I see screen 1.
If I press a button on screen 1 then it will navigate from screen 1 to screen 2.
When I press tab1 or tab2 and then I press tab3 again then I see screen 1.
I want to see screen 2 (because that's what should be at the top of the stack from the NativeStackNavigator right?).
Did the stack from NativeStackNavigator reset?
Did the whole NativeStackNavigator render again?
What causes this behavior?
Code BottomTabNavigator:
export type MainNavigationBarParam = {
tab1: undefined;
tab2: undefined;
tab3: undefined;
};
const Tab = getMainNavigationBarTabNavigator();
export const tab3Stack = createNativeStackNavigator();
export function MainNavigationBar() {
const sizeToUse = 25;
return (
<Tab.Navigator
screenOptions={defaultScreenOptions()}
barStyle={{backgroundColor: theme.colors.primary}}>
<Tab.Screen
name="tab1"
component={Component1}
/>
<Tab.Screen
name="tab2"
component={Component2}
/>
<Tab.Screen
name="tab3"
component={Component3}
/>
</Tab.Navigator>
);
}
function defaultScreenOptions() {
const screenOptions: any = {
headerShown: false,
tabBarHideOnKeyboard: true,
};
if (Platform.OS === 'web') {
screenOptions.swipeEnabled = false;
}
return screenOptions;
}
Code Component3:
export type Tab3Stack ParamList = {
Screen1: undefined;
Screen2: {id: string; name: string};
};
export default function Component3() {
const [topComponentHeight, setTopComponentHeight] = useState(0);
function onLayout(event: LayoutChangeEvent) {
if ('top' in event.nativeEvent.layout) {
const withTop = event.nativeEvent.layout as unknown as {top: number};
setTopComponentHeight(withTop.top);
}
event;
}
return (
<View style={navContainerStyle(topComponentHeight).navContainer} onLayout={onLayout}>
<tab3Stack.Navigator screenOptions={{headerShown: false}}>
<tab3Stack.Screen
name={'Screen1'}
component={Screen1}
/>
<tab3Stack.Screen
name={'Screen2'}
component={Screen2}
/>
</tab3Stack.Navigator>
</View>
);
}
Update 1
Reproduced the code above for 2 tabs: https://snack.expo.dev/#jacobdel/182747
The problem does not occur on snack, only in my app.
Console in chrome:
No errors or warnings.
Sometimes this is shown, but after disabling the quill editor it doesn't appear anymore
Update 2
Cause is found: getMainNavigationBarTabNavigator(); from BottomTabNavigator
File MainNavigationBarTabNavigator.ts looks like this:
import {createMaterialBottomTabNavigator} from '#react-navigation/material-bottom-tabs';
import {MainNavigationBarParam} from './MainNavigationTabs';
export function getMainNavigationBarTabNavigator() {
return createMaterialBottomTabNavigator<MainNavigationBarParam>();
}
While MainNavigationBarTabNavigator.web.ts looks like this:
import {createMaterialTopTabNavigator} from '#react-navigation/material-top-tabs';
import {MainNavigationBarParam} from './MainNavigationTabs';
export function getMainNavigationBarTabNavigator() {
return createMaterialTopTabNavigator<MainNavigationBarParam>();
}
Snack only works as intended when using MainNavigationBarTabNavigator.ts
Update 3
Stuck..
Replacing the code from MainNavigationBarTabNavigator.web.ts with the code from MainNavigationBarTabNavigator.ts does not show the intended behavior as shown in the Snack example.
There seems to be an issue with the navigation event handling of #react-navigation/material-top-tabs.
If we handle this on our own, then the nested stack is not reset. We can prevent the default navigation action by using a tabPress event listener and calling e.preventDefault.
In your case, this is done as follows for screen2 (which is the nested stack).
<Tab.Screen
name="screen2"
component={Screen2}
listeners={({ navigation, route }) => ({
tabPress: (e) => {
e.preventDefault();
navigation.navigate({ name: route.name, merge: true });
},
})}
/>
Notice that this works fine on the phone, but has some issues on the web. If we navigate fast between screen2 and screen1 multiple times, then the navigation does not work at all. I haven't found the root cause for this issue.
However, we can make this work on the web as well as on the phone by providing a custom top navigation bar using the tabBar prop of the tab navigator. We override the default behavior of the tabPress function.
The original component is exported and is named MaterialTopTabBar. Sadly, it does not provide the possibility to provide a custom onTabPress function via props.
I have forked the component and 'fixed' (without knowing what exactly is going wrong here) the onTabPress function.
import {
useTheme,
} from '#react-navigation/native';
import Color from 'color';
import * as React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { TabBar, TabBarIndicator } from 'react-native-tab-view';
export default function FixedTabBarTop({
state,
navigation,
descriptors,
...rest
}) {
const { colors } = useTheme();
const focusedOptions = descriptors[state.routes[state.index].key].options;
const activeColor = focusedOptions.tabBarActiveTintColor ?? colors.text;
const inactiveColor =
focusedOptions.tabBarInactiveTintColor ??
new Color(activeColor).alpha(0.5).rgb().string();
return (
<TabBar
{...rest}
navigationState={state}
scrollEnabled={focusedOptions.tabBarScrollEnabled}
bounces={focusedOptions.tabBarBounces}
activeColor={activeColor}
inactiveColor={inactiveColor}
pressColor={focusedOptions.tabBarPressColor}
pressOpacity={focusedOptions.tabBarPressOpacity}
tabStyle={focusedOptions.tabBarItemStyle}
indicatorStyle={[
{ backgroundColor: colors.primary },
focusedOptions.tabBarIndicatorStyle,
]}
indicatorContainerStyle={focusedOptions.tabBarIndicatorContainerStyle}
contentContainerStyle={focusedOptions.tabBarContentContainerStyle}
style={[{ backgroundColor: colors.card }, focusedOptions.tabBarStyle]}
getAccessibilityLabel={({ route }) =>
descriptors[route.key].options.tabBarAccessibilityLabel
}
getTestID={({ route }) => descriptors[route.key].options.tabBarTestID}
onTabPress={({ route }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (!event.defaultPrevented) {
navigation.navigate({ name: route.name, merge: true });
}
}}
onTabLongPress={({ route }) =>
navigation.emit({
type: 'tabLongPress',
target: route.key,
})
}
renderIcon={({ route, focused, color }) => {
const { options } = descriptors[route.key];
if (options.tabBarShowIcon === false) {
return null;
}
if (options.tabBarIcon !== undefined) {
const icon = options.tabBarIcon({ focused, color });
return (
<View style={[styles.icon, options.tabBarIconStyle]}>{icon}</View>
);
}
return null;
}}
renderLabel={({ route, focused, color }) => {
const { options } = descriptors[route.key];
if (options.tabBarShowLabel === false) {
return null;
}
const label =
options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: route.name;
if (typeof label === 'string') {
return (
<Text
style={[styles.label, { color }, options.tabBarLabelStyle]}
allowFontScaling={options.tabBarAllowFontScaling}
>
{label}
</Text>
);
}
return label({ focused, color });
}}
renderBadge={({ route }) => {
const { tabBarBadge } = descriptors[route.key].options;
return tabBarBadge?.() ?? null;
}}
renderIndicator={({ navigationState: state, ...rest }) => {
return focusedOptions.tabBarIndicator ? (
focusedOptions.tabBarIndicator({
state: state,
...rest,
})
) : (
<TabBarIndicator navigationState={state} {...rest} />
);
}}
/>
);
}
const styles = StyleSheet.create({
icon: {
height: 24,
width: 24,
},
label: {
textAlign: 'center',
textTransform: 'uppercase',
fontSize: 13,
margin: 4,
backgroundColor: 'transparent',
},
});
I have used it as follows.
export function MainNavigationBar() {
return (
<Tab.Navigator
tabBar={props => <FixedTabBarTop {...props} />}
screenOptions={defaultScreenOptions()}>
<Tab.Screen
name="screen1"
component={Screen1}
/>
<Tab.Screen
name="screen2"
component={Screen2}
/>
</Tab.Navigator>
);
}
I have updated your snack. You might want to submit an issue on GitHub as well. It feels like a bug.

React Navigation - undefined is not an object (evaluating 'this.navigation.navigate')

I am following this tutorial to implement a switch navigator for user authentication: https://snack.expo.io/#react-navigation/auth-flow-v3.
However, this.navigation.navigate appears to undefined when I try to navigate to the next screen.
undefined is not an object (evaluating 'this.props.navigation.navigate')
I am using expo for my app, and I've already looked at the solutions posted to a similar question at React Native - navigation issue "undefined is not an object (this.props.navigation.navigate)" to no avail.
import * as React from 'react';
import { createBottomTabNavigator } from 'react-navigation-tabs';
import profile from './app/screens/profile.js'
import home from './app/screens/home.js'
import createCompetition from './app/screens/create.js'
import explore from './app/screens/explore.js'
import Icon from 'react-native-vector-icons/MaterialIcons'
import login from './app/screens/login.js';
import { f } from './config/config.js';
import { ActivityIndicator, AsyncStorage, Button, StatusBar, StyleSheet, View } from 'react-native';
import { createStackNavigator } from 'react-navigation-stack';
import { createAppContainer, createSwitchNavigator } from 'react-navigation';
/**
* Tab Stack is the Bottom Navigator for the different pages
*/
const TabStack = createBottomTabNavigator(
{
Home: {
screen: home,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Icon name="home" size={25} style={{ color: tintColor }} />
),
},
},
Explore: {
screen: explore,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Icon name="search" size={25} style={{ color: tintColor }} />
),
}
},
Profile: {
screen: profile,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Icon name="person" size={25} style={{ color: tintColor }} />
),
}
},
Create: {
screen: createCompetition,
navigationOptions: {
tabBarIcon: ({ tintColor }) => (
<Icon name="add" size={25} style={{ color: tintColor }} />
),
}
},
},
{
tabBarOptions: {
showIcon: true,
showLabel: false,
activeTintColor: 'black',
style: { backgroundColor: 'white', }
},
},
)
/**
* Loading Screen during authorization process
*/
class AuthLoadingScreen extends React.Component {
constructor() {
super();
this._bootstrapAsync();
}
// Fetch the token from storage then navigate to our appropriate place
_bootstrapAsync = async () => {
f.auth().onAuthStateChanged(function (user) { //checks if user is signed in or out
this.props.navigation.navigate(user ? 'App' : 'Auth');
})
};
// Render any loading content that you like here
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStack = createStackNavigator({ Home: TabStack });
const AuthStack = createStackNavigator({ Login: login });
const RootStack = createSwitchNavigator(
{
AuthLoading: AuthLoadingScreen,
App: AppStack,
Auth: AuthStack,
},
{
initialRouteName: 'AuthLoading',
}
);
const App = createAppContainer(RootStack);
export default App;
You are not giving access to this to your _bootstrapAsync function and your onAuthStateChanged callback. Just pass the callback inside of it using arrow function, as it autobinds the current function to the current app this
_bootstrapAsync = async () => {
f.auth().onAuthStateChanged((user) => { //checks if user is signed in or out
this.props.navigation.navigate(user ? 'App' : 'Auth');
})
};
The problem is with function keyword, which doesnt bind this keyword. Better replace it with ES6 arrow functions which implictly binds this to the inner scope :
f.auth().onAuthStateChanged((user) => { //checks if user is signed in or out
this.props.navigation.navigate(user ? 'App' : 'Auth');
})
Hope it helps .feel free for doubts

react Navigation 3.x open drawer from header button?

I want to create a header on top with title for each screen and button on the right to open the drawer in react navigation 3.x
In the code below the header does not show.
//Updated with Current code
import React, { Component } from 'react';
import { Button } from 'react-native';
import {
createStackNavigator,
createDrawerNavigator,
createAppContainer
} from 'react-navigation';
import MyHomeScreen from './components/HomeScreen';
import MyNotificationsScreen from './components/ProfileScreen';
const MyDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: MyHomeScreen
},
Notifications: {
screen: MyNotificationsScreen
}
},
{
initialRouteName: 'Home',
navigationOptions: navigationOptionsHeader
}
);
const navigationOptionsHeader = ({ navigation }) => {
return {
headerTitle: 'MY Home',
headerRight: (
<Button
onPress={() => navigation.toggleDrawer()}
title="Info"
color="#222"
/>
)
};
};
const AppContainer = createAppContainer(MyDrawerNavigator);
class App extends Component {
render() {
return <AppContainer />;
}
}
export default App;
Use this inside your screen class
static navigationOptions = ({ navigation }) => {
return {
title: 'Home',
headerLeft: (
< Icon name="menu" size={30} style={{marginStart:10}} backgroundColor="#000000" onPress={() => navigation.openDrawer()} > < /Icon>
),
};
};
try this
const MyDrawerNavigator = createDrawerNavigator(
{
Home: {
screen: MyHomeScreen
},
Notifications: {
screen: MyNotificationsScreen
}
},
{
initialRouteName: 'Home'
navigationOptions: navigationOptionsHeader,
}
);
const navigationOptionsHeader=({navigation})=>{
return {
headerRight: (
<Button
onPress={() => navigation.toggleDrawer();
}
title="Info"
color="#222"
/>
)
};
}
you can also add other stuffs in header like this
const navigationOptionsHeader=({navigation})=>{
return {
headerRight: (
<Button
onPress={() => navigation.toggleDrawer();
}
title="Info"
color="#222"
/>
)
headerLeft : <headerLeft/>,
title: //Header Title
headerStyle: { backgroundColor: '#161616', height:48, },
headerTitleStyle:{ color:'#cd9bf0', fontWeight: '400', alignSe
};
}
The navigationoptions had been renamed as defaultNavigationOptions in v3.
Please refer the documentation from https://reactnavigation.org/docs/en/headers.html
For React Navigation 5
Use the prop options as a function:
<Stack.Screen
name="screen name"
component={ScreenComponent}
options={({ navigation }) => ({
headerRight: (props) => {
return <Button onPress={() => navigation.toggleDrawer() }} />
}
})}
/>
https://reactnavigation.org/docs/upgrading-from-4.x/#configuring-the-navigator
For react navigation 5.x
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
headerLeft: () => (
<View>
<Icon
onPress={() => navigation.toggleDrawer()}
name="menu"
/>
</View>
),
}}
/>

withNavigation can only be used on a view hierarchy of a navigator

I'm getting the error:
Invariant Violation: withNavigation can only be used on a view
hierarchy of a navigator. The wrapped component is unable to get
access to navigation from props or context
I don't know why, because I'm using withNavigation in other components in my app and it works. I don't see a difference in the components that it works on to the one that causes the error.
Code:
the component:
const mapStateToProps = (state: State): Object => ({
alertModal: state.formControls.alertModal
})
const mapDispatchToProps = (dispatch: Dispatch<*>): Object => {
return bindActionCreators(
{
updateAlertModalHeight: updateAlertModalHeight,
updateAlertModalIsOpen: updateAlertModalIsOpen,
updateHasYesNo: updateAlertModalHasYesNo
},
dispatch
)
}
class AlertModalView extends Component<AlertModalProps, State> {
render(): Node {
return (
<View style={alertModalStyle.container}>
<PresentationalModal
style={presentationalModalStyle}
isOpen={this.props.alertModal.isOpen}
title={this.props.alertModal.title}
navigation={this.props.navigation}
updateHasYesNo={this.props.updateHasYesNo}
message={this.props.alertModal.message}
updateAlertModalHeight={this.props.updateAlertModalHeight}
viewHeight={this.props.alertModal.viewHeight}
hasYesNo={this.props.alertModal.hasYesNo}
yesClicked={this.props.alertModal.yesClicked}
updateAlertModalIsOpen={this.props.updateAlertModalIsOpen}
/>
</View>
)
}
}
// $FlowFixMe
const AlertModalViewComponent = connect(
mapStateToProps,
mapDispatchToProps
)(AlertModalView)
export default withNavigation(AlertModalViewComponent)
the stackNavigator:
import React from 'react'
import { View, SafeAreaView } from 'react-native'
import Icon from 'react-native-vector-icons/EvilIcons'
import Add from '../product/add/view'
import Login from '../user/login/view'
import Search from '../product/search/query/view'
import { Image } from 'react-native'
import { StackNavigator, DrawerNavigator, DrawerItems } from 'react-navigation'
const AddMenuIcon = ({ navigate }) => (
<View>
<Icon
name="plus"
size={30}
color="#FFF"
onPress={() => navigate('DrawerOpen')}
/>
</View>
)
const SearchMenuIcon = ({ navigate }) => (
<Icon
name="search"
size={30}
color="#FFF"
onPress={() => navigate('DrawerOpen')}
/>
)
const Stack = {
Login: {
screen: Login
},
Search: {
screen: Search
},
Add: {
screen: Add
}
}
const DrawerRoutes = {
Login: {
name: 'Login',
screen: Login
},
'Search Vegan': {
name: 'Search',
screen: StackNavigator(Stack.Search, {
headerMode: 'none'
}),
navigationOptions: ({ navigation }) => ({
drawerIcon: SearchMenuIcon(navigation)
})
},
'Add vegan': {
name: 'Add',
screen: StackNavigator(Stack.Add, {
headerMode: 'none'
}),
navigationOptions: ({ navigation }) => ({
drawerIcon: AddMenuIcon(navigation)
})
}
}
const CustomDrawerContentComponent = props => (
<SafeAreaView style={{ flex: 1, backgroundColor: '#3f3f3f', color: 'white' }}>
<View>
<Image
style={{
marginLeft: 20,
marginBottom: 0,
marginTop: 0,
width: 100,
height: 100,
resizeMode: 'contain'
}}
square
source={require('../../images/logo_v_white.png')}
/>
</View>
<DrawerItems {...props} />
</SafeAreaView>
)
const Menu = StackNavigator(
{
Drawer: {
name: 'Drawer',
screen: DrawerNavigator(DrawerRoutes, {
initialRouteName: 'Login',
drawerPosition: 'left',
contentComponent: CustomDrawerContentComponent,
contentOptions: {
activeTintColor: '#27a562',
inactiveTintColor: 'white',
activeBackgroundColor: '#3a3a3a'
}
})
}
},
{
headerMode: 'none',
initialRouteName: 'Drawer'
}
)
export default Menu
Here I render the StackNavigator which is Menu in my app component:
import React, { Component } from 'react'
import Menu from './menu/view'
import Props from 'prop-types'
import { Container } from 'native-base'
import { updateAlertModalIsOpen } from './formControls/alertModal/action'
import AlertModalComponent from './formControls/alertModal/view'
import UserLoginModal from './user/login/loginModal/view'
class Vepo extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => {})
store.dispatch(this.props.fetchUserGeoCoords())
store.dispatch(this.props.fetchSearchQueryPageCategories())
store.dispatch(this.props.fetchCategories())
}
componentWillUnmount() {
this.unsubscribe()
}
render(): Object {
return (
<Container>
<Menu store={this.context} />
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
<UserLoginModal />
</Container>
)
}
}
Vepo.contextTypes = {
store: Props.object
}
export default Vepo
and my root component:
export const store = createStore(
rootReducer,
vepo,
composeWithDevTools(applyMiddleware(createEpicMiddleware(rootEpic)))
)
import NavigationService from './navigationService'
export const App = () => (
<Provider store={store}>
<Vepo
fetchUserGeoCoords={fetchUserGeoCoords}
fetchSearchQueryPageCategories={fetchSearchQueryPageCategories}
fetchCategories={fetchCategories}
/>
</Provider>
)
AppRegistry.registerComponent('vepo', () => App)
I have changed my Vepo component to this to implement the answer by vahissan:
import React, { Component } from 'react'
import Menu from './menu/view'
import Props from 'prop-types'
import { Container } from 'native-base'
import { updateAlertModalIsOpen } from './formControls/alertModal/action'
import AlertModalComponent from './formControls/alertModal/view'
import UserLoginModal from './user/login/loginModal/view'
import NavigationService from './navigationService'
class Vepo extends Component {
componentDidMount() {
const { store } = this.context
this.unsubscribe = store.subscribe(() => {})
store.dispatch(this.props.fetchUserGeoCoords())
store.dispatch(this.props.fetchSearchQueryPageCategories())
store.dispatch(this.props.fetchCategories())
}
componentWillUnmount() {
this.unsubscribe()
}
render(): Object {
return (
<Container>
<Menu
store={this.context}
ref={navigatorRef => {
NavigationService.setTopLevelNavigator(navigatorRef)
}}>
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
</Menu>
<UserLoginModal />
</Container>
)
}
}
Vepo.contextTypes = {
store: Props.object
}
export default Vepo
No errors, but the alertModal no longer displays
In react-navigation, the main StackNavigator creates a context provider and the navigation prop will be made available to any component below its level in the component tree if they use the context consumer.
Two ways to access the navigation prop using context consumer is to either add the component to the StackNavigator, or use the withNavigation function. However because of how React's context API works, any component that uses withNavigation function must be below the StackNavigator in the component tree.
If you still want to access the navigation prop regardless of the position in component tree, you will have to store the ref to the StackNavigator in a module. Following guide from react-navigation will help you do that https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html
If you are using react-navigation version 5, use useNavigation hook with a functional component. This hook injects the navigation object into the functional component. Here is the link to the docs:
https://reactnavigation.org/docs/use-navigation/
Vahissan's answer is correct but I could not get it working because of various differences in my code from https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html like my stackNavigator not being a component, it is just an object.
What I managed to do was get the AlertModal component to be a child of the stackNavigator and thus receive the navigation prop by adding it to my StackNavigator's ContentComponent. The code is as above but int the CustomDrawerContentComponent just making it like this:
const CustomDrawerContentComponent = props => (
<SafeAreaView style={{ flex: 1, backgroundColor: '#3f3f3f', color: 'white' }}>
<View>
<Image
style={{
marginLeft: 20,
marginBottom: 0,
marginTop: 0,
width: 100,
height: 100,
resizeMode: 'contain'
}}
square
source={require('../../images/logo_v_white.png')}
/>
</View>
<DrawerItems {...props} />
<AlertModalComponent
yesClicked={() => {
updateAlertModalIsOpen(false)
}}
/>
</SafeAreaView>
)
For anyone who might be interested, you need to npm install #react-navigation/native #react-navigation/compat #react-navigation/stack for withNavigation to work in the recent version

How To Reload View Tap on TabNavigator in React Native

I want to reload the tabNavigator when the user changse the tab each time. the lifecycle method of react native doesn't get called when user changes the tab. Then how can it be possible to reload tab in TabNavigator :
The below example have two Tabs : 1) Home 2)Events. Now I want to refresh the event Tab when user returns from the home tab.
EXPO LINK : Expo Tab Navigator
Code :
import React, {Component} from 'react';
import {View, StyleSheet, Image, FlatList, ScrollView, Dimensions } from 'react-native';
import { Button, List, ListItem, Card } from 'react-native-elements' // 0.15.0
//import { Constants } from 'expo';
import { TabNavigator, StackNavigator } from 'react-navigation'; // 1.0.0-beta.11
//image screen width and height defs
const windowWidth = Dimensions.get('window').width;
const windowHeight = Dimensions.get('window').height;
export default class App extends Component {
render() {
//const { navigate } = this.props.navigation;
return (
<TabsNav />
)
}
}
class MyHomeScreen extends Component {
render() {
return (
<View>
<Image
resizeMode="cover"
style={{ width: windowWidth * .85, height: windowHeight * 0.3}}
source={{uri: 'http://www.ajaxlibrary.ca/sites/default/files/media/logo.png?s358127d1501607090'}}
/>
<Button
onPress={() => this.props.navigation.navigate('Notifications')}
title="Go to notifications"
/>
</View>
);
}
}
class AplEvents extends Component {
static navigationOptions = {
tabBarLabel: 'Events List',
tabBarIcon: ({ tintColor }) => (
<Image
source={{uri: 'https://facebook.github.io/react/img/logo_og.png'}}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
};
constructor(props) {
super(props);
this.state = {
data: [],
error: null,
};
}
// when component mounts run the function fetch
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const url = `http://www.ajaxlibrary.ca/?q=calendar-test`;
fetch(url)
.then((res) => res.json())
.then((res) => {
this.setState({
data: [...this.state.data, ...res.nodes],
error: res.error || null,
});
})
.catch(error => {
this.setState( error );
});
};
render() {
const { navigate } = this.props.navigation;
return (
<List containerStyle={{ marginTop: 0, borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
//squareAvatar
title={`${item.node.title}\n${item.node.Program_Location}`}
subtitle={item.node.Next_Session}
avatar={{ uri: item.node.Image }}
containerStyle={{ borderBottomWidth: 0 }}
// save params to pass to detailed events screen
onPress={() => navigate('Details', {title: `${item.node.title}`,
body: `${item.node.Body}`,
date: `${item.node.Date}`,
Next_Session: `${item.node.Next_Session}`,
Program_Location: `${item.node.Program_Location}`,
Nid: `${item.node.Nid}`,
Image: `${item.node.Image}`,
Run_Time: `${item.node.Run_Time}`})}
/>
)}
keyExtractor={item => item.node.Nid}
/>
</List>
);
}
}
class EventDetails extends Component {
static navigationOptions = {
title: 'EventDetails'
};
render() {
const { params } = this.props.navigation.state;
let pic = {
uri: `${params.Image}`
};
//const pic = { params.Image };
return (
<ScrollView>
<Card
title={params.title}
>
<Image
resizeMode="cover"
style={{ width: windowWidth * .85, height: windowHeight * 0.3}}
source={pic}
/>
<Button style={{marginTop: 10}}
icon={{name: 'date-range'}}
backgroundColor='#03A9F4'
fontFamily='Lato'
buttonStyle={{borderRadius: 0, marginLeft: 0, marginRight: 0, marginBottom: 0}}
title='Add to Calendar'
/>
<ListItem
title="Event Description"
subtitle={params.body}
hideChevron='true'
/>
<ListItem
title="Date"
subtitle={`${params.Next_Session}\n Run Time - ${params.Run_Time}`}
hideChevron='true'
/>
<ListItem
title="Location"
subtitle={params.Program_Location}
hideChevron='true'
/>
</Card>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
icon: {
width: 26,
height: 26,
},
});
const EventStack = StackNavigator({
EventList: {
screen: AplEvents,
navigationOptions: {
title: "APL Event Listing",
}
},
Details: {
screen: EventDetails,
},
TabsNav: { screen: MyHomeScreen}
});
const TabsNav = TabNavigator({
Home: {
screen: MyHomeScreen,
navigationOptions: {
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor }) => (
<Image
source={{uri: 'https://upload.wikimedia.org/wikipedia/de/thumb/9/9f/Twitter_bird_logo_2012.svg/154px-Twitter_bird_logo_2012.svg.png'}}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
},
},
EventList: {
screen: EventStack,
navigationOptions: {
tabBarLabel: 'Events',
tabBarIcon: ({ tintColor }) => (
<Image
source={{uri: 'https://upload.wikimedia.org/wikipedia/de/thumb/9/9f/Twitter_bird_logo_2012.svg/154px-Twitter_bird_logo_2012.svg.png'}}
style={[styles.icon, {tintColor: tintColor}]}
/>
),
},
},
}, {
tabBarOptions: {
activeTintColor: '#e91e63',
},
});
React Native Tab Navigation has an option prop as unmountOnBlur set it to true and it will reload the tab screens every time you click on tabs.
<Tab.Screen name={"Profile"} component={ProfileScreen} options={{unmountOnBlur: true}} />
Doc/Ref - RN Bottom tab Navigator docs
.
Update - There is a hook called as useIsFocused in '#react-navigation/native'.
Use this with useEffect to re-render the screen every time it is focused or used .
import { useIsFocused } from '#react-navigation/native';
const isFocused = useIsFocused();
useEffect(() => { yourApiCall(); }, [isFocused])
look at this link. My problem is solving thanks to this.
<Tabs.Navigator
initialRouteName="Home"
tabBar={(props) => (
<TabBar {...props} />
)}>
<Tabs.Screen
name="Home"
component={HomeView}
options={{ unmountOnBlur: true }}
listeners={({ navigation }) => ({
blur: () => navigation.setParams({ screen: undefined }),
})}
/>
</Tabs.Navigator>
https://github.com/react-navigation/react-navigation/issues/6915#issuecomment-692761324
There's many long discussion about this from react-native issue 314, 486, 1335, and finally we got a way to handle this, after Sep 27, 2017, react-navigation version v1.0.0-beta.13:
New Features
Accept a tabBarOnPress param (#1335) - #cooperka
So here we go,
Usage:
const MyTabs = TabNavigator({
...
}, {
tabBarComponent: TabBarBottom /* or TabBarTop */,
tabBarPosition: 'bottom' /* or 'top' */,
navigationOptions: ({ navigation }) => ({
tabBarOnPress: (scene, jumpToIndex) => {
console.log('onPress:', scene.route);
jumpToIndex(scene.index);
},
}),
});
I wasn't able to get this to work, and after checking the React Navigation documentation, found this, which seems to suggest that later versions (I'm using 1.0.0-beta.27) changed the method signature to a single object:
tabBarOnPress Callback to handle tap events; the argument is an object
containing:
the previousScene: { route, index } which is the scene we are leaving
the scene: { route, index } that was tapped
the jumpToIndex method
that can perform the navigation for you
https://reactnavigation.org/docs/en/tab-navigator.html#tabbaronpress
Given that, and the code from beausmith here, I put this together.
navigationOptions: ({ navigation }) => ({
tabBarOnPress: (args) => {
if (args.scene.focused) { // if tab currently focused tab
if (args.scene.route.index !== 0) { // if not on first screen of the StackNavigator in focused tab.
navigation.dispatch(NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: args.scene.route.routes[0].routeName }) // go to first screen of the StackNavigator
]
}))
}
} else {
args.jumpToIndex(args.scene.index) // go to another tab (the default behavior)
}
}
})
Note that you'll need to import NavigationActions from react-navigation for this to work.
Hope this helps somebody :)