How to call this.props from a static navigationOptions - react-navigation - react-native

I am trying to call a handlelogout() in onPress of button in the header of a stack navigator and then in the handlelogout() I am calling this.props.logout action which will call a navigation reducer to reset to login screen.....but this.props.logout doesnt call an action....nothing happens
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
console.log("navigation", navigation);
return {
title: "Profile List",
gesturesEnabled: false,
headerLeft: null,
headerRight: <Button title={"Logout"} onPress={() => params.handlelogout && params.handlelogout({ state: navigation.state })} />
}
};
this is my handlelogout function
handlelogout(state) {
console.log("in handlelogout", this.props.logout, state);
this.props.logout;
}
i am attaching the log i printed in the console
here I am binding the logout to mapdispatchprops
function mapDispatchToProps(dispatch) {
return bindActionCreators(ReduxActions, dispatch);
}

You can try to put the button inside another element then handle your logout from that element/class.
import ButtonForLogout from './test/buttonforlogout';
...
static navigationOptions = ({ navigation }) => {
const { params } = navigation.state;
console.log("navigation", navigation);
return {
title: "Profile List",
gesturesEnabled: false,
headerLeft: null,
headerRight: <ButtonForLogout />
}
};
buttonforlogout.js
import React, { Component } from 'react';
import { View, Button } from 'react-native';
import { connect } from 'react-redux';
import { ReduxActions } from './youractions';
class ButtonForLogout extends Component {
render(){
return(
<View>
<Button title={"Logout"} onPress={this.props.logout} />
</View>
);
}
}
const mapStateToProps = state => ({});
export default connect(mapStateToProps, ReduxActions)(ButtonForLogout);
Something like that.

Related

Change tabBarIcon when a redux state changes

I have defined navigationOptions under App.js for a flow like so:
App.js
const intimationsFlow = createStackNavigator({
Feed: FeedContainer,
Edit: EditContainer
});
intimationsFlow.navigationOptions = ({ navigation }) => {
let tabBarVisible = true;
if (navigation.state.index > 0)
tabBarVisible = false;
return {
title: '',
tabBarIcon: ({ focused }) => {
const { pushNotificationSeen } = store.getState();
console.log('pushNotificationSeen', pushNotificationSeen);
let i;
if(pushNotificationSeen) {
if (focused) {
i = <FontAwesomeIcon icon={'bell'} size={29} color={'#3780BE'} />
} else {
i = <FontAwesomeIcon icon={'bell'} size={29} color={'#393939'} />
}
} else {
if (focused) {
updatePushNotificationSeen(true);
i = <FontAwesomeIcon icon={'bell'} size={29} color={'#3780BE'} />
} else {
i = <><FontAwesomeIcon icon={'bell'} size={29} color={'#393939'} /><Badge status="error" badgeStyle={{ position: 'absolute', top: -26, right: -13 }} /></>
}
}
return i;
},
tabBarVisible
};
};
const AppNavigator = createSwitchNavigator({
ResolveAuth: ResolveAuthScreen,
mainFlow
});
const App = createAppContainer(AppNavigator);
export default () => {
return <Provider store={store}>
<SafeAreaProvider>
<App ref={navigatorRef => { setTopLevelNavigator(navigatorRef) }} />
</SafeAreaProvider>
</Provider>
};
I want to update the tabBarIcon based on whether a push notification has already been seen or not. If the push notification has not already been seen then I show a badge instead.
The problem here is that I could only fetch the state when there is an activity on the tab bar. But what I want is that whenever the status of pushNotificationSeen is updated, the tarBarIcon should get re-rendered.
Please suggest if it is possible otherwise how could it be achieved. Thanks.
you have to listen push notifications change in reducer at componentWillReceiveProps
class YourComponent extends React.Component {
{...}
static navigationOptions = {
title: ({ state }) => `there is ${state.params && state.params.pushNotificationSeen ? state.params.pushNotificationSeen : ''}`,
{...}
}
{...}
componentWillReceiveProps(nextProps) {
if (nextProps.pushNotificationSeen) {
this.props.navigation.setParams({ pushNotificationSeen });
}
}
}
const connectedSuperMan = connect(state => ({ pushNotificationSeen: state.ReducerYo.pushNotificationSeen }))(YourComponent);
I was able to find a decent solution.
What really I wanted was a way to access redux store from the React Navigation component. React navigation components like any other react components can be connected to the redux store. However, in this particular case, in order to connect the react navigation component, what I wanted was to create an custom extended navigator as per this suggestion.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Badge } from 'react-native-elements';
import { createStackNavigator } from 'react-navigation-stack';
import { FontAwesomeIcon } from '#fortawesome/react-native-fontawesome';
import { NavigationEvents } from 'react-navigation';
import FeedContainer from '../../screens/feed/FeedContainer';
import EditContainer from '../../screens/edit/EditContainer';
import { updatePushNotificationSeen } from '../../store/push-notification-seen/actions';
const IntimationsFlow = createStackNavigator({
Feed: FeedContainer,
Edit: EditContainer
});
class IntimationsFlowNavigator extends Component {
static router = IntimationsFlow.router;
static navigationOptions = ({ navigation }) => {
let tabBarVisible = true;
if (navigation.state.index > 0)
tabBarVisible = false;
return {
title: '',
tabBarIcon: ({ focused }) => {
let i;
if (!navigation.state.params || navigation.state.params.pushNotificationSeen) {
if (focused)
i = <FontAwesomeIcon icon={'bell'} size={29} color={'#3780BE'} />;
else
i = <FontAwesomeIcon icon={'bell'} size={29} color={'#393939'} />;
} else {
if (focused)
i = <FontAwesomeIcon icon={'bell'} size={29} color={'#3780BE'} />;
else
i = <>
<FontAwesomeIcon icon={'bell'} size={29} color={'#393939'} />
<Badge status="error" badgeStyle={{ position: 'absolute', top: -26, right: -13 }} />
</>;
}
return i;
},
tabBarVisible
};
};
componentDidUpdate(prevProps) {
const { navigation, pushNotificationSeen } = this.props;
if (pushNotificationSeen !== prevProps.pushNotificationSeen)
navigation.setParams({ pushNotificationSeen });
}
render() {
const { navigation } = this.props;
return <>
<NavigationEvents
onDidFocus={() => { if (!this.props.pushNotificationSeen) updatePushNotificationSeen(true) }}
onDidBlur={() => { if (!this.props.pushNotificationSeen) updatePushNotificationSeen(true) }}
/>
<IntimationsFlow navigation={navigation} />
</>;
}
}
const mapStateToProps = ({ pushNotificationSeen }) => {
return { pushNotificationSeen };
};
export default connect(mapStateToProps, null)(IntimationsFlowNavigator);
Every time there was an update in the props. I was setting new value for navigation.state.params.pushNotificationSeen like so navigation.setParams({ pushNotificationSeen }); in the componentDidUpdate lifecycle method so as to use it in the navigationOptions static method. (We can't directly access components props in the navigationOptions method since it is a static member).
Any side-effects that are needed to be performed on focus/blur of the tab can be achieved via NavigationEvents as per this suggestion.

Passing props to tabNavigator's screens

In my app, I want to pass some data in my createMaterialTopTabNavigator (const NewTab) to both it's child screens (Graphical and Tabular). The data in child screens changes on the basis of the dropdown value in the custom header that will be above createMaterialTopTabNavigator. As soon as the value from the dropdown is selected, it will trigger a fetch request in both the Graphical and Tabular Screen.
Here is my App.js where I am routing all my screens.
const NewTab = createMaterialTopTabNavigator({
Graphical: Graphical,
Tabular: Tabular
});
const DailyStack = createStackNavigator({
Dashboard,
Daily,
Login,
SalesDashboard,
About : {
screen: NewTab,
navigationOptions : {
header : <CustomHeader />
}
}
})
const MonthlyStack = createStackNavigator({
Monthly: Monthly
})
const RangeStack = createStackNavigator({
Range: Range
})
export const BottomTabNavigation = createBottomTabNavigator({
Daily: {
screen: DailyStack
},
Monthly: {
screen: MonthlyStack
},
Range: {
screen: RangeStack
}
})
const DashboardStackNavigator = createStackNavigator({
BottomTabNavigation:BottomTabNavigation,
}, {
headerMode: 'none'
})
export const AppDrawerNavigator = createDrawerNavigator({
DashboardStackNavigator,
Login: {
screen: Login
},
Logout: {
screen: Logout
}
})
const OpenNav = createSwitchNavigator({
Splash: {screen: SplashScreen},
Login: { screen: Login },
Home: { screen: Home }
})
const AppNavigator = createStackNavigator({
OpenNav,
AppDrawerNavigator: { screen: AppDrawerNavigator },
ClaimsDashboard: ClaimsDashboard
},{
headerMode:'none'
});
class App extends Component {
constructor(){
super();
console.disableYellowBox = true;
}
render() {
return (
<AppContainer />
);
}
};
const AppContainer = createAppContainer(AppNavigator);
export default App;
CustomHeader.js
import React, { Component } from 'react'
import { Text, View, Picker } from 'react-native'
import Icon from 'react-native-vector-icons/AntDesign'
export default class CustomHeader extends Component {
constructor(props) {
super(props)
this.state = {
user: ''
}
}
updateUser = (user) => {
this.setState({ user: user })
}
render() {
return (
<View>
<Icon name="arrowleft" size={30} onPress={navigation.goBack()} />
<View>
<Picker selectedValue = {this.state.user} onValueChange = {this.updateUser}>
<Picker.Item label = "Steve" value = "steve" />
<Picker.Item label = "Ellen" value = "ellen" />
<Picker.Item label = "Maria" value = "maria" />
</Picker>
</View>
</View>
)
}
}
Considering all files included in App.js, I tried using screenprops for the same, but is not sure how to do it.
In dire need for solution. Please help.
Yes, you can use screenProps here as I made below, but I strongly recommend using third party to manage state like redux, mobx, etc... or simply using context.
class DailyStackWithData extends React.Component {
static router = DailyStack.router
state = {
user: 'steve'
}
setUser = user => this.setState({ user })
componentDidMount(){
this.props.navigation.setParams({ setUser: this.setUser });
}
render(){
const { user } = this.state;
const { navigation } = this.props;
return (<DailyStack screenProps={user} navigation={navigation}/>)
}
}
export const BottomTabNavigation = createBottomTabNavigator({
Daily: {
screen: DailyStack
},
...
});
CustomHeader.js
import React, { Component } from 'react';
import { Text, View, Picker } from 'react-native';
import Icon from 'react-native-vector-icons/AntDesign';
import { withNavigation } from "react-navigation";
class CustomHeader extends Component {
constructor(props) {
super(props)
this.state = {
user: ''
}
}
updateUser = user => {
const setUser = this.props.navigation.getParam('setUser', () => {});
setUser(user);
this.setState({ user: user });
}
render() {
return (
<View>
<Icon name="arrowleft" size={30} onPress={()=>{}} />
<View>
<Picker selectedValue = {this.state.user} onValueChange = {this.updateUser}>
<Picker.Item label = "Steve" value = "steve" />
<Picker.Item label = "Ellen" value = "ellen" />
<Picker.Item label = "Maria" value = "maria" />
</Picker>
</View>
</View>
)
}
}
export default withNavigation(CustomHeader)
and in Graphical or Tabular screen you can get that variable by screenProps props. I've made an example here

How to override navigation options in functional component?

To override navigation options using class components, it would be something like
export default class SomeClass extends Component {
static navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('headerTitle'),
}
}
componentDidMount() {
this.props.navigation.setParams({ headerTitle: someVariableThatComesFromExternalCall })
}
...
}
But how can I do this using functional component??
export default function SomeFunctionalCompoenent({ navigation }) {
// How and Where do I change the header title ?
useEffect(() => { navigation.setParams({ headerTitle: someVariableThatComesFromExternalCall })})
return (
...
)
}
You still need to define navigationOptions on your functional component. You do it like this:
export default function SomeFunctionalComponent({ navigation }) {
useEffect(() => {
navigation.setParams({
headerTitle: someVariableThatComesFromExternalCall
})
}, [])
}
SomeFunctionalComponent.navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('headerTitle'),
}
}
I've got a suspicion the accepted answer isn't for the currently latest version of react navigation (5), and it definitely doesn't work for this version, so here's a working example for #react-navigation/native v5.7.2 :
export default function SomeFunctionalComponent({ navigation }) {
useLayoutEffect(() => {
navigation.setOptions({
headerTitle: 'some other title',
})
}, [])
}
I've used this to access react context - to get the user's name and avatar so I can set a nice title bar for them. I've pasted in the code for this in case it helps anyone:
import React, { useContext, useLayoutEffect } from 'react';
import UserContext from '../context/UserContext';
const HomeScreen = ({ navigation }) => {
const userContext = useContext(UserContext);
useLayoutEffect(() => {
navigation.setOptions({
title : userContext.name,
headerLeft : () => (
<TouchableOpacity
onPress={() => {
navigation.openDrawer();
}}
>
<FastImage
style={styles.userPhoto}
resizeMode={FastImage.resizeMode.cover}
source={{ uri: userContext.avatar }}
/>
</TouchableOpacity>
),
});
}, [ userContext ]);
return (
// etc
);
}

Passing Navigator object in renderItem

I'm struggling to make Navigator object visible in List Component.
Here the code explained: as you can see in RootDrawer, I have Concept component. It simply shows a list of items based on a id passed in param.
Each item points to another Concept with another id, but I get
undefined is not an object (evaluating 'navigation.navigate')
when I press on that <RippleButton> with onPress={() => navigation.navigate('Concept',{id:12}). The problem here I'm not passing the Navigation object correctly. How can I solve?
main Navigator drawer
const RootDrawer = DrawerNavigator(
{
Home: {
screen: StackNavigator({
Home: { screen: Home }
})
},
Search: {
screen: StackNavigator({
Cerca: { screen: Search },
Concept: { screen: Concept },
})
}
}
);
Concept component
export default class Concept extends Component {
loading = true
static navigationOptions = ({ navigation,state }) => ({
headerTitle: "",
title: `${navigation.state.params.title}` || "",
})
constructor(props) {
super(props);
}
async componentDidMount(){
}
render() {
const { params } = this.props.navigation.state;
const id = params ? params.id : null;
const { t, i18n, navigation } = this.props;
const Concept = DB.get(id)
return (
<View>
<ScrollView>
<List data={Concept.array|| []} title={"MyTile"} navigation={navigation} />
</ScrollView>
</View>
);
}
}
List Component
class List extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const { navigation } = this.props;
}
_renderItem = (item,navigation) => (
<RippleButton
id={item.id}
onPress={() => navigation.navigate('Concept', { id: 12, otherParam: 'anything you want here'})}> //this line throws the error
<Text>{item.Term}</Text>
</RippleButton>
)
render() {
const { navigation } = this.props;
return (
<View>
<FlatList
data={this.props.data}
renderItem={({item}) => this._renderItem(item, navigation)}
/>
</View>)
}
}
Instead of passing the navigation prop, you can try using the withNavigation HOC.
Where your List Component is defined:
import { withNavigation } from 'react-navigation`
....
export default withNavigation(List)
Now the List component will have access to the navigation prop

React-Native SwitchNavigator don't provide new props in root

I have problem with receiving new props in root stack navigator. I have 2 screens in stack navigator: list and edit item. On list screen i click a edit button and dispatch data to store - it works. But in the edit screen i edit data and dispatch new list with new element (for test). List screen dont receive new list. Can you help me?
App.js
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'CATEGORIES_CHANGED':
return {
...state,
categories: action.data
};
case 'SET_CATEGORY_INFO':
return {
...state,
categoryInfo: action.data
};
default:
return state;
}
};
const store = createStore(reducer);
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppNavigator/>
</Provider>
);
}
}
AppNavigator.js
import React from 'react';
import { createSwitchNavigator } from 'react-navigation';
import MainTabNavigator from './MainTabNavigator';
import { connect } from 'react-redux';
const AppNavigator = createSwitchNavigator({
Main: MainTabNavigator
});
export default AppNavigator;
MainTabNavigator.js
import React from 'react';
import {Platform} from 'react-native';
import {createStackNavigator, createBottomTabNavigator} from 'react-navigation';
import { connect } from 'react-redux';
...
const CategoriesStack = createStackNavigator({
CategoriesListScreen: {
screen: CategoriesListScreen,
},
CategoryInfoScreen: {
screen: CategoryInfoScreen,
},
CategoryEditScreen: {
screen: CategoryEditScreen,
},
});
CategoriesStack.navigationOptions = {
tabBarLabel: 'Categories',
tabBarIcon: ({focused}) => (
<TabBarIcon
focused={focused}
name={Platform.OS === 'ios' ? 'ios-link' : 'md-link'}
/>
),
};
...
const bottomTabNavigator = createBottomTabNavigator({
CategoriesStack,
...
});
export default bottomTabNavigator;
CategoriesListScreen.js
import { connect } from 'react-redux';
class CategoriesListScreen extends React.Component {
render() {
const cats = this.state.categories;
return (
<ScrollView style={styles.container}>
{cats.map((category, i) => {
return (
<TouchableOpacity key={category.id} style={
(i === cats.length - 1) ?
{...styles.categoryItem, ...styles.categoryItemLast} :
styles.categoryItem
} onPress={()=>{this.onPressCategory(category)}}>
<View style={{
...styles.categoryLabel, ...{
backgroundColor: category.color
}
}}>
<Icon name={category.icon} size={25} style={styles.categoryIcon}
color={category.iconColor}/>
</View>
<Text>{category.title}</Text>
</TouchableOpacity>
)
})}
</ScrollView>
);
}
componentWillReceiveProps(nextProps) {
console.log(nextProps);
}
componentWillMount() {
const categories = this.props.categories;
this.setState({
categories: categories
});
}
onPressCategory(category) {
this.props.setCategoryInfo(category);
this.props.navigation.navigate('CategoryInfoScreen', {});
}
}
function mapStateToProps(state) {
return {
categories: state.categories
}
}
function mapDispatchToProps(dispatch) {
return {
setCategoryInfo: (category) => dispatch({ type: 'SET_CATEGORY_INFO', data: category })
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CategoriesListScreen)
CategoryEditScreen.js
import { connect } from 'react-redux';
class CategoryEditScreen extends React.Component {
static navigationOptions = ({navigation}) => {
return {
title: 'Edit Category',
headerRight: <Button onPress={() => {navigation.getParam('categoryChangedDispatch')()}} title="Save"/>
}
};
render() {
const category = this.state.category;
...
}
componentWillMount() {
const category = this.props.categoryInfo;
this.setState({
category: category
});
}
componentDidMount() {
this.props.navigation.setParams({
categoryChangedDispatch: this.categoryChangedDispatch.bind(this)
});
}
categoryChangedDispatch() {
let cats = this.props.categories;
cats.push({
id: 3,
title: 'My third category',
color: '#7495e7',
icon: 'airplane',
iconColor: '#2980B9'
});
this.props.categoryChanged(cats);
this.props.navigation.navigate('CategoryInfoScreen', {});
}
}
function mapStateToProps(state) {
return {
categories: state.categories,
categoryInfo: state.categoryInfo
}
}
function mapDispatchToProps(dispatch) {
return {
categoryChanged: (categories) => dispatch({ type: 'CATEGORIES_CHANGED', data: categories }),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CategoryEditScreen)
It seems it's related to the fact you update your local state (this.state.categories) based on the categories property, only during the componentWillMount phase but not when props are updated (which should happen after you dispatched the new data).
Try using this.props.categories directly within your CategoriesListScreen component.
before
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'CATEGORIES_CHANGED':
return {
...state,
categories: action.data
};
default:
return state;
}
};
after
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'CATEGORIES_CHANGED':
return {
...state,
categories: Object.assign([], action.data)
};
default:
return state;
}
};
Reducer has problem. I made the absolutely new array. It works! But I think it isn't normal :)