Change tabBarIcon when a redux state changes - react-native

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.

Related

Screen State not Updating from AsyncStorage when going back

I'm building a React Native app.
My app has 5 Screens: Home (initialRouteName), DeckPage, QuestionPage, NewCardPage, NewDeckPage. (in this order)
I'm using Redux for state management. The state is updating from AsyncStorage.
The component that does the fetching is the class component "Home" by dispatching the "fetching" function in componentDidMount.
Component NewCardPage, NewDeckPAge are also updating the state with new content by dispatching the same fetching function as the Home when a button is pressed.
My problem appears when I want to delete a Deck component from inside DeckPage parent component. The function that does this job has this functionality: after removing the item from AsyncStorage, updates the STATE, and moves back to Screen HOME. The issue is that when I go back to HOME component the state doesn't update with the latest info from AsyncStorage.
This is not the case when I'm doing the same operation in the other 2 components NewCardPage, NewDeckPage.
I'll paste the code below:
import React, { Component } from "react";
import { connect } from "react-redux";
import { View, Text, StyleSheet, FlatList } from "react-native";
import Header from "../components/Header";
import AddDeckButton from "../components/AddDeckButton";
import DeckInList from "../components/DeckInList";
import { receiveItemsAction } from "../redux/actions";
class Home extends Component {
componentDidMount() {
this.props.getAsyncStorageContent();
}
renderItem = ({ item }) => {
return <DeckInList {...item} />;
};
render() {
const { items } = this.props;
// console.log(items);
const deckNumber = Object.keys(items).length;
return (
<View style={styles.container}>
<Header />
<View style={styles.decksInfoContainer}>
<View style={styles.deckNumber}>
<View style={{ marginRight: 50 }}>
<Text style={styles.deckNumberText}>{deckNumber} Decks</Text>
</View>
<AddDeckButton />
</View>
<View style={{ flex: 0.9 }}>
<FlatList
data={Object.values(items)}
renderItem={this.renderItem}
keyExtractor={(item) => item.title}
/>
</View>
</View>
</View>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.items,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getAsyncStorageContent: () => dispatch(receiveItemsAction()),
};
};
-----------DECKPAGE COMPONENT------------
import React from "react";
import { View, StyleSheet } from "react-native";
import Deck from "../components/Deck";
import { useSelector, useDispatch } from "react-redux";
import { removeItemAction, receiveItemsAction } from "../redux/actions";
import AsyncStorage from "#react-native-community/async-storage";
const DeckPage = ({ route, navigation }) => {
const { title, date } = route.params;
const questions = useSelector((state) => state.items[title].questions);
const state = useSelector((state) => state.items);
const dispatch = useDispatch();
// const navigation = useNavigation();
const handleRemoveIcon = async () => {
await AsyncStorage.removeItem(title, () => {
dispatch(receiveItemsAction());
navigation.goBack();
});
};
console.log(state);
return (
<View style={styles.deckPageContainer}>
<Deck
handleRemoveIcon={handleRemoveIcon}
title={title}
questions={questions}
date={date}
/>
</View>
);
};
-----------This is my ACTIONS file----------
import AsyncStorage from "#react-native-community/async-storage";
export const RECEIVE_ITEMS = "RECEIVE_ITEMS";
// export const REMOVE_ITEM = "REMOVE_ITEM";
export const receiveItemsAction = () => async (dispatch) => {
const objectValues = {};
try {
const keys = await AsyncStorage.getAllKeys();
if (keys.length !== 0) {
const jsonValue = await AsyncStorage.multiGet(keys);
if (jsonValue != null) {
for (let element of jsonValue) {
objectValues[element[0]] = JSON.parse(element[1]);
}
dispatch({
type: RECEIVE_ITEMS,
payload: objectValues,
});
} else {
return null;
}
}
} catch (e) {
console.log(e);
}
};
-----This is my REDUCERS file----
import { RECEIVE_ITEMS, REMOVE_ITEM } from "./actions";
const initialState = {
};
const items = (state = initialState, action) => {
switch (action.type) {
case RECEIVE_ITEMS:
return {
...state,
...action.payload,
};
// case REMOVE_ITEM:
// return {
// ...state,
// ...action.payload,
// };
default:
return state;
}
}
export default items;
-----This is my UTILS file----
import AsyncStorage from "#react-native-community/async-storage";
export const removeDeckFromAsyncStorage = async (title)=>{
try{
await AsyncStorage.removeItem(title);
}
catch(e){
console.log(`Error trying to remove deck from AsyncStorage ${e}`);
}
}

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 :)

Unrecognized font family 'entypo'

I'm using the create react native app by the expo team to build an app. Using Icon component from react-native-elements to create a react navigation header feature. Snippet below:
const Navigator = new createStackNavigator({
Home: {
screen: Home,
path: '/',
navigationOptions: ({ navigation }) => ({
title: 'Home',
headerStyle: {
backgroundColor: 'black'
},
headerLeft: (
<Icon
name="menu"
size={30}
type="entypo"
style={{ paddingLeft: 10 }}
/>
),
}),
},
})
I encountered this error:
After numerous iterations, I found this supposed work around 1st and 2nd by the expo team and implemented it this way below for the app but still encountering the same problems.
import Expo from "expo";
import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { AppLoading, Asset, Font } from 'expo';
import { FontAwesome, Ionicons } from '#expo/vector-icons';
import { connect } from 'react-redux'
import { Auth } from 'aws-amplify';
import AuthTabs from './auth/Tabs';
import Nav from './navs/Navigator';
import Home from "./components/Home";
class App extends React.Component {
state = {
user: {},
isLoading: true,
isLoadingComplete: false,
};
async componentDidMount() {
StatusBar.setHidden(true)
try {
const user = await Auth.currentAuthenticatedUser()
this.setState({ user, isLoading: false })
} catch (err) {
this.setState({ isLoading: false })
}
}
async componentWillReceiveProps(nextProps) {
try {
const user = await Auth.currentAuthenticatedUser()
this.setState({ user })
} catch (err) {
this.setState({ user: {} })
}
}
render() {
if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
return(
<AppLoading
startAsync={this._loadResourcesAsync}
onError={this._handleLoadingError}
onFinish={this._handleFinishLoading}
/>
);
}
else{
if (this.state.isLoading) return null
let loggedIn = false
if (this.state.user.username) {
loggedIn = true
}
if (loggedIn) {
return (
<Nav />
)
}
return (
<AuthTabs />
)
}
}
_loadResourcesAsync = async () => {
console.log("fonts loading..")
const entypoFont = {
'entypo': require('../node_modules/#expo/vector-icons/fonts/Entypo.ttf')
};
const fontAssets = cacheFonts([ FontAwesome.font, Ionicons.font, entypoFont ]);
console.log("loaded all fonts locally")
await Promise.all([...fontAssets]);
console.log("promisified all fonts")
};
_handleLoadingError = error => {
console.warn(error);
};
_handleFinishLoading = () => {
this.setState({ isLoadingComplete: true });
};
}
function cacheFonts(fonts){
return fonts.map(font => Font.loadAsync(font))
}
const mapStateToProps = state => ({
auth: state.auth
})
export default connect(mapStateToProps)(App)
What are my doing wrong and how can it be configured appropriately? Thank you

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

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.

How to pass redux store in react native between pages?

In react native app I have 2 pages. If I upload a redux store with data on the 2 page, then return to the 1 page - how can I access the store with the uploaded data from the 2 page? So is there a way to access the store with data from all of the pages in react native?
Maybe simoke example or where to read?
Thanks
1page.js
class ScreenHome extends Component{
static navigationOptions = {
title: 'ScreenHome',
};
constructor(props){
super(props)
console.log("PROPS: ",props);
}
render() {
const { navigate } = this.props.navigation;
return (
<View>
<Button
title="Go to load data page"
onPress={() => navigate('New', { name: 'Jane' })}
/>
<Button
title="Get redux data"
onPress={() => {console.log(this.props)}}
/>
</View>
);
}
}
class ScreenRegister extends Component{
static navigationOptions = {
title: 'ScreenRegister',
};
render(){
return <Text>ScreenRegister</Text>
}
}
const MainScreenNavigator = DrawerNavigator({
Recent: {
screen: ScreenHome
},
All: {
screen: ScreenRegister
},
});
export default SimpleApp = StackNavigator({
Home: {
screen: MainScreenNavigator
},
Chat: {
screen: ScreenHome
},
New: {
screen: testScreen
}
});
const mapStateToProps = (state) => {
const {items, isFetching, done} = state.myTestData
return {testScreen:{items, isFetching, done}};
}
const mapDispatchToProps = (dispatch) => {
return {
getNewItems: () => {
dispatch(fetchData());
}
}
}
export default someTest = connect(
mapStateToProps,
mapDispatchToProps
)(SimpleApp)
2page.js
class testScreen extends Component{
static navigationOptions = {
title: 'testScreen.js',
};
_reduxStuff = () => {
this.props.getNewItems();
}
render() {
const { navigate } = this.props.navigation;
const {done, items, isFetching} = this.props.testScreen;
return (
<View>
<Text>Some new screen</Text>
<Button
title="Load Data"
onPress={() => this._reduxStuff()}
/>
</View>
);
}
}
const mapStateToProps = (state) => {
const {items, isFetching, done} = state.myTestData
return {testScreen:{items, isFetching, done}};
}
const mapDispatchToProps = (dispatch) => {
return {
getNewItems: () => {
dispatch(fetchData());
}
}
}
export default FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(testScreen)
There should be a container for each page, a store for data you want to access between pages and actions to changing this store. By using mapStateToProps you can pass this store to the container of the page. You can find good example in here.
On your first container you'll need to make your async calls to fill your store.
You can do a dispatch on your componentWillMount() and populate your store with the received data.