How to pass functions from parent to any children and sub-children using Navigator and WithSecurityScreen in React Native? - react-native

I am quite new in React Native and I am trying to understand how function written in a parent Component could be passed (inherited) to any children and sub-children. In particular I am using a library to internationalise my App using:
import * as RNLocalize from 'react-native-localize'
import i18n from 'i18n-js'
But I noticed that I have to implement the translate(...) function for each Component of the whole project and this seems to be exaggerated because it requires a lot of work to implement the translation feature (I followed this tutorial).
Please, note that I have a basic understanding how to pass a function or some data using this.props, so I am not asking how props works from a parent to a single child. What I am asking is: how to avoid to repeat the code from //BEGIN ... to //END... (please see WithSecurityScreen file) and to avoid to repeat the implementation of handleLocalizationChange, RNLocalize.addEventListener, RNLocalize.removeEventListener and translate.
Please also note that the translation library works, test is provided at following line of WithSecurityScreen:
const SecurityScreen = () => <View><Text>{translate('USER_SURNAME')}😂</Text></View>;
But I am not be able to pass translate(...) function to each components of the whole project.
The project structure is:
App.js (wraps SecureApp.js)
SecureApp.js (wrapped in App.js and runs WithSecurityScreen.js)
WithSecurityScreen.js (wraps routes to views, e.g. Welcome.js)
Welcome.js (main view)
App.js
import { withSecurityScreen } from './src/components/withSecurityScreen'
import App from "./SecureApp.js"
export default withSecurityScreen(App);
SecureApp.js
const MainNavigator = createStackNavigator({
Home: {
screen: Welcome,
navigationOptions: {
headerShown: false
}
},
UserProfile: {
screen: CoreApp,
navigationOptions: {
headerShown: false
}
},
NumPad: {
screen: NumPad,
navigationOptions: {
header: 'PIN Creation',
headerShown: false
}
}, /* , navigationOptions: {headerLeft: () => null} */
QrScan: {
screen: QrScan, navigationOptions: {
header: 'QR Scan',
headerShown: false
}
},
...
});
export default createAppContainer(MainNavigator);
WithSecurityScreen.js
// START: https://heartbeat.fritz.ai/how-to-use-react-native-localize-in-react-native-apps-3bb3d510f801
import * as RNLocalize from 'react-native-localize'
import i18n from 'i18n-js'
import memoize from 'lodash.memoize'
const translationGetters = {
en: () => require('./../../assets/locales/en/en.json'),
it: () => require('./../../assets/locales/it/it.json')
};
const translate = memoize(
(key, config) => i18n.t(key, config),
(key, config) => (config ? key + JSON.stringify(config) : key)
)
const setI18nConfig = () => {
const fallback = { languageTag: 'en' }
const { languageTag } =
RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
fallback
translate.cache.clear()
i18n.translations = { [languageTag]: translationGetters[languageTag]() }
i18n.locale = languageTag
}
// END: https://heartbeat.fritz.ai/how-to-use-react-native-localize-in-react-native-apps-3bb3d510f801
const SecurityScreen = () => <View><Text>{translate('USER_SURNAME')}😂</Text></View>;
const showSecurityScreenFromAppState = appState =>
['background', 'inactive'].includes(appState);
const withSecurityScreenIOS = Wrapped => {
return class WithSecurityScreen extends React.Component {
constructor(props) {
super(props)
setI18nConfig()
}
state = {
showSecurityScreen: showSecurityScreenFromAppState(AppState.currentState)
};
componentDidMount() {
AppState.addEventListener('change', this.onChangeAppState)
RNLocalize.addEventListener('change', this.handleLocalizationChange)
}
componentWillUnmount() {
AppState.removeEventListener('change', this.onChangeAppState)
RNLocalize.removeEventListener('change', this.handleLocalizationChange)
}
handleLocalizationChange = () => {
setI18nConfig()
.then(() => this.forceUpdate())
.catch(error => {
console.error(error)
})
}
onChangeAppState = nextAppState => {
const showSecurityScreen = showSecurityScreenFromAppState(nextAppState);
this.setState({showSecurityScreen})
};
render() {
return this.state.showSecurityScreen
? <SecurityScreen/>
: <Wrapped {...this.props} />
}
}
};
const withSecurityScreenAndroid = Wrapped => Wrapped;
export const withSecurityScreen = Platform.OS === 'ios'
? withSecurityScreenIOS
: withSecurityScreenAndroid;
Welcome.js
export default class Welcome extends Component {
let username = 'UserName';
render() {
return (
<View style={styles.container}>
<LinearGradient colors={globalStyles.colors.gradientGreen} style={{flex: 1}}>
<View style={styles.upperView}><Text style={styles.upperViewText}>{this.props.translate('WELCOME_TEXT')}{this.username}</Text>
</View>
</LinearGradient>
</View>
);
}
}
I get following error:

First of all in your case you can declare translate function in separate js file locale.js and can declare all your translation logic in that file and export the functions translate and setI18nConfig
local.js
import * as RNLocalize from 'react-native-localize'
import i18n from 'i18n-js'
import memoize from 'lodash.memoize'
const translationGetters = {
en: () => require('./../../assets/locales/en/en.json'),
it: () => require('./../../assets/locales/it/it.json')
};
export const translate = memoize(
(key, config) => i18n.t(key, config),
(key, config) => (config ? key + JSON.stringify(config) : key)
)
export const setI18nConfig = () => {
const fallback = { languageTag: 'en' }
const { languageTag } =
RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) ||
fallback
translate.cache.clear()
i18n.translations = { [languageTag]: translationGetters[languageTag]() }
i18n.locale = languageTag
}
and import this functions in your components where you want to use this like
App.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import { setI18nConfig, translate } from './locale';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
setI18nConfig(); // This should be called only once through out the app at the time of changing locale.
return (
<View>
<Text> translate ('key_from_json_to_label') </Text>
</View>
);
}
}
For more details you can refer this repo where I have implemented the same.
Hope this will help.

i use import i18n from "i18next", for translation.
Here is the config file:
//config/i18n
import i18n from "i18next";
import { reactI18nextModule, initReactI18next } from "react-i18next";
import translationFR from '../translation/fr/translation.json';
import translationEN from '../translation/en/translation.json';
import DeviceInfo from 'react-native-device-info';
let locale = DeviceInfo.getDeviceLocale().substring(0, 2);
if (locale != 'fr') {
locale = 'en';
}
// the translations
const resources = {
en: translationEN,
fr: translationFR,
};
i18n
.use(reactI18nextModule) // passes i18n down to react-i18next
.init({
resources: resources,
lng: locale,
fallbackLng: ['en', 'fr'],
keySeparator: false, // we do not use keys in form messages.welcome
interpolation: {
escapeValue: false // react already safes from xss
}
});
export default i18n;
One of the uses is to
import i18n from 'config/i18n'
and use translate in file like this
i18n.t('bottomTab:home_tab')
You can also wrap component with withNamespaces from 'react-i18next', like this:
export default withNamespaces([], {wait: true})(Welcome)
and then access translation with:
this.props.t('welcome_message')

Related

How to test a custom react hook which uses useNavigation from 'react-navigation-hooks' using Jest?

I have a custom react hook 'useSample' which uses useNavigation and useNavigationParam
import { useContext } from 'react'
import { useNavigation, useNavigationParam } from 'react-navigation-hooks'
import sampleContext from '../sampleContext'
import LoadingStateContext from '../LoadingState/Context'
const useSample = () => {
const sample = useContext(sampleContext)
const loading = useContext(LoadingStateContext)
const navigation = useNavigation()
const Mode = !!useNavigationParam('Mode')
const getSample = () => {
if (Mode) {
return sample.selectors.getSample(SAMPLE_ID)
}
const id = useNavigationParam('sample')
sample.selectors.getSample(id)
navigation.navigate(SAMPLE_MODE_ROUTE, { ...navigation.state.params}) // using navigation hook here
}
return { getSample }
}
export default useSample
I need to write unit tests for the above hook using jest and I tried the following
import { renderHook } from '#testing-library/react-hooks'
import sampleContext from '../../sampleContext'
import useSample from '../useSample'
describe('useSample', () => {
it('return sample data', () => {
const getSample = jest.fn()
const sampleContextValue = ({
selectors: {
getSample
}
})
const wrapper = ({ children }) => (
<sampleContext.Provider value={sampleContextValue}>
{children}
</sampleContext.Provider>
)
renderHook(() => useSample(), { wrapper })
})
})
I got the error
'react-navigation hooks require a navigation context but it couldn't be found. Make sure you didn't forget to create and render the react-navigation app container. If you need to access an optional navigation object, you can useContext(NavigationContext), which may return'
Any help would be appreciated!
versions I am using
"react-navigation-hooks": "^1.1.0"
"#testing-library/react-hooks":"^3.4.1"
"react": "^16.11.0"
You have to mock the react-navigation-hooks module.
In your test:
import { useNavigation, useNavigationParam } from 'react-navigation-hooks';
jest.mock('react-navigation-hooks');
And it's up to you to add a custom implementation to the mock. If you want to do that you can check how to mock functions on jest documentation.
for me, soved it by usingenter code here useRoute():
For functional component:
import * as React from 'react';
import { Button } from 'react-native';
import { useNavigation } from '#react-navigation/native';
function MyBackButton() {
const navigation = useNavigation();
return (
<Button
title="Back"
onPress={() => {
navigation.goBack();
}}
/>
);
}
For class component:
class MyText extends React.Component {
render() {
// Get it from props
const { route } = this.props;
}
}
// Wrap and export
export default function(props) {
const route = useRoute();
return <MyText {...props} route={route} />;
}

How to pass localization info from this.context in react component to its child consts?

I have implemented localization for React-native app according to this file as LocalizationContext.js:
import React from 'react';
import Translations, {DEFAULT_LANGUAGE} from '../constants/Translations';
import AsyncStorage from '#react-native-community/async-storage';
import * as RNLocalize from 'react-native-localize';
const APP_LANGUAGE = 'appLanguage';
export const LocalizationContext = React.createContext({
Translations,
setAppLanguage: () => {},
appLanguage: DEFAULT_LANGUAGE,
initializeAppLanguage: () => {},
});
export const LocalizationProvider = ({children}) => {
const [appLanguage, setAppLanguage] = React.useState(DEFAULT_LANGUAGE);
const setLanguage = language => {
Translations.setLanguage(language);
setAppLanguage(language);
AsyncStorage.setItem(APP_LANGUAGE, language);
};
const initializeAppLanguage = async () => {
const currentLanguage = await AsyncStorage.getItem(APP_LANGUAGE);
if (!currentLanguage) {
let localeCode = DEFAULT_LANGUAGE;
const supportedLocaleCodes = Translations.getAvailableLanguages();
const phoneLocaleCodes = RNLocalize.getLocales().map(
locale => locale.languageCode,
);
phoneLocaleCodes.some(code => {
if (supportedLocaleCodes.includes(code)) {
localeCode = code;
return true;
}
});
setLanguage(localeCode);
} else {
setLanguage(currentLanguage);
}
};
return (
<LocalizationContext.Provider
value={{
Translations,
setAppLanguage: setLanguage,
appLanguage,
initializeAppLanguage,
}}>
{children}
</LocalizationContext.Provider>
);
};
and it works fine in different screens but App.js file which is something like:
const MainTabs = createBottomTabNavigator(
{
Profile: {
screen: ProfileStack,
navigationOptions: {
// tabBarLabel: Translations.PROFILE_TAB,
},
},
HomePage: {
screen: HomeStack,
navigationOptions: {
tabBarLabel: Translations.HOME_TAB,
},
},
},
{
initialRouteName: 'HomePage'
},
},
);
export default class App extends Component {
// static contextType = LocalizationContext;
render() {
// const Translations = this.context.Translations;
// console.log(Translations.PROFILE_TAB);
return (
<LocalizationProvider>
<SafeAreaView style={{flex: 1}}>
<AppNavigator />
</SafeAreaView>
</LocalizationProvider>
);
}
}
I do access Translation in App component as you can find them in commented lines, but how can I pass related information to some const like tab titles? Translations.PROFILE_TAB is undefined.
I ended up changing this into a service.
Also I use redux and pass the store in to get user preferences and set them:
import * as RNLocalize from 'react-native-localize';
import { saveUserOptions } from '../actions/login';
import LocalizedStrings from 'react-native-localization';
export const DEFAULT_LANGUAGE = 'ar';
let _reduxStore;
function setStore(store){
_reduxStore = store
}
const _translations = {
en: {
WELCOME_TITLE: 'Welcome!',
STEP1: 'Step One',
SEE_CHANGES: 'See Your Changes',
CHANGE_LANGUAGE: 'Change Language',
LANGUAGE_SETTINGS: 'Change Language',
BACK: 'Back'
},
ar: {
WELCOME_TITLE: 'صباحك فُل!',
...
}
};
let translation = new LocalizedStrings(_translations);
const setAppLanguage = language => {
translation.setLanguage(language);
_reduxStore.dispatch(saveUserOptions('user_language',language))
};
const initializeAppLanguage = async () => {
const currentLanguage = _reduxStore.getState().login.user_language
if (!currentLanguage) {
let localeCode = DEFAULT_LANGUAGE;
const supportedLocaleCodes = translation.getAvailableLanguages();
const phoneLocaleCodes = RNLocalize.getLocales().map(
locale => locale.languageCode,
);
phoneLocaleCodes.some(code => {
if (supportedLocaleCodes.includes(code)) {
localeCode = code;
return true;
}
});
setAppLanguage(localeCode);
} else {
setAppLanguage(currentLanguage);
}
};
export default {
setStore,
translation,
setAppLanguage,
initializeAppLanguage
}
I need to first setup things in my top main component:
LocalizationService.setStore(store)
...
// add the below to componentDidMount by which time persisted stores are populated usually
LocalizationService.initializeAppLanguage()
where I need to get strings I do:
import LocalizationService from '../../utils/LocalizationService';
....
static navigationOptions = () => {
// return here was needed otherwise the translation didn't work
return {
title: LocalizationService.translation.WELCOME_TITLE,
}
}
EDIT
to force update of title you will need to set a navigation param:
this.props.navigation.setParams({ otherParam: 'Updated!' })
** Further Edit **
The props navigation change hack only works for the current screen, if we want to refresh all screens we need to setParams for all screens. This could possibly be done using a listener on each screen or tying the screen navigationOptions to the redux state.
I'm using NavigationService (see https://reactnavigation.org/docs/en/navigating-without-navigation-prop.html) so I've created the following function to run through my screens and setparam on all of them and force an update on all:
function setParamsForAllScreens(param, value) {
const updateAllScreens = (navState) => {
// Does this route have more routes in it?
if (navState.routes) {
navState.routes.forEach((route) => {
updateAllScreens(route)
})
}
// Does this route have a name?
else if (navState.routeName) {
// Does it end in Screen? This is a convention we are using
if (navState.routeName.endsWith('Screen')) {
// this is likely a leaf
const action = NavigationActions.setParams({
params: {
[param]: value
},
key: navState.key,
})
_navigator.dispatch(action)
}
}
}
if (_navigator && _navigator.state)
updateAllScreens(_navigator.state.nav)
}

React Navigation 3 - Calling Inside Saga

I am developing an App that uses redux-sagas and react-navigation v3.
The problem that I am facing right now is that I want to use the method navigate inside the saga. The action actually is dispatched, I can see it in the logger, but it doesn't change my screen.
Here are my files:
App.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Provider } from 'react-redux';
import store from './store';
import Navigator from './routes/index.js'
export default function App() {
return (
<Provider store={store}>
<Navigator />
</Provider>
);
}
routes.js
import React from "react";
import { View, Text } from "react-native";
import {
createBottomTabNavigator,
createSwitchNavigator,
createStackNavigator,
createAppContainer
} from "react-navigation";
import { FontAwesome } from '#expo/vector-icons';
// Screens
import LoginScreen from '../screens/Login.js';
// Routes
import Leads from './leads.js';
import Settings from './settings.js';
const TabsStack = createBottomTabNavigator(
{
Leads: Leads ,
Settings: Settings,
},
{
defaultNavigationOptions: ({ navigation }) => ({
tabBarIcon: ({ focused, horizontal, tintColor }) => {
const { routeName } = navigation.state;
const iconSize = 28;
let iconName;
if (routeName === 'Leads') {
iconName = 'home'
} else if(routeName === 'Settings'){
iconName = 'cogs'
}
return <FontAwesome name={iconName} size={iconSize} color={tintColor} />
},
}),
tabBarOptions: {
activeTintColor: '#208DFF',
inactiveTintColor: '#cecece',
showLabel: false
},
}
);
const LoginStack = createStackNavigator({
Login: {
screen: LoginScreen,
navigationOptions: ({navigation}) => ({
header: null
})
}
});
const Router = createSwitchNavigator(
{
Login: LoginStack,
Tabs: TabsStack,
},
{
initialRouteName: "Login"
}
);
export default createAppContainer(Router);
store.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from "redux-saga";
import { createLogger } from 'redux-logger';
import sagas from '../sagas/';
import reducers from '../reducers/';
const sagaMiddleware = createSagaMiddleware();
const loggerMiddleware = createLogger({collapsed: true});
const store = createStore(
reducers,
applyMiddleware(sagaMiddleware, loggerMiddleware)
);
sagaMiddleware.run(sagas);
export default store;
and finally my sagas
import { takeLatest, call, put } from "redux-saga/effects";
import { NavigationActions } from "react-navigation";
import * as sessionActions from '../actions/sessions';
import * as navigateActions from '../actions/navigation.js';
import { login } from '../api/index.js';
import { AsyncStorage } from 'react-native';
export function* loginRequest({email, password}){
let response = yield call(login, {email, password})
if(!response.error){
saveToken(response.auth_token);
yield put({type: sessionActions.SESSION_LOGIN_REQUEST_SUCCESS, user: response})
}else{
yield put({type: sessionActions.SESSION_LOGIN_REQUEST_FAILURE, error: response.error})
}
}
export function* loginRequestSuccessful(){
console.log("Teste! 2");
yield put(NavigationActions.navigate({ routeName: 'Leads' }))
}
async function saveToken(token) {
try {
return await AsyncStorage.setItem("auth_token", token);
} catch (err) {
console.error(err);
}
}
function* loginSaga(){
yield takeLatest(sessionActions.SESSION_LOGIN_REQUEST, loginRequest);
yield takeLatest(sessionActions.SESSION_LOGIN_REQUEST_SUCCESS, loginRequestSuccessful);
}
export default loginSaga;
I get this from the logger
action Navigation/NAVIGATE # 20:23:07.668
RemoteConsole.js:80 prev state {sessions: {…}}
RemoteConsole.js:80 action {type: "Navigation/NAVIGATE", routeName: "Leads", ##redux-saga/SAGA_ACTION: true}
RemoteConsole.js:80 next state {sessions: {…}}
I don't know how can I navigate to another screen inside a sagas function.
The only way I got navigation working is using this.props.navigation inside the component, but I need to work inside the sagas.
In my project, I do it as follow:
Step 1: Set ref for the AppNavigationContainer like
AppContainer = createAppContainer(this.Switch);
constructor(props: any) {
super(props);
this.state = {};
}
handleNavigationChange = (
prevState: NavigationState,
newState: NavigationState,
action: NavigationAction,
) => {
};
render(): React.ReactNode {
return (
<Root>
{/* <StatusBar barStyle="dark-content" /> */}
<this.AppContainer
ref={(navigatorRef: any) => {
serviceProvider.NavigatorService().setContainer(navigatorRef);
}}
onNavigationStateChange={this.handleNavigationChange}
/>
</Root>
);
}
Step 2: Add the navigation service which is called by the ref in this.AppContainer
import { NavigationActions, NavigationParams, NavigationRoute, NavigationContainerComponent, NavigationContainer } from 'react-navigation';
export default class NavigatorService {
container?: NavigationContainerComponent & NavigationContainer;
setContainer = (container: NavigationContainerComponent & NavigationContainer): void => {
this.container = container;
}
getContainer = (container: NavigationContainerComponent & NavigationContainer): any => {
return container;
}
navigate = (routeName: string, params?: NavigationParams): void => {
if (this.container) {
this.container.dispatch(
NavigationActions.navigate({
routeName,
params,
}),
);
}
}
goBack = (): void => {
if (this.container) {
this.container.dispatch(
NavigationActions.back(),
);
}
}
getCurrentRoute(): NavigationRoute | null {
if (!this.container || !this.container.state.nav) {
return null;
}
return this.container.state.nav.routes[this.container.state.nav.index] || null;
}
}
Step 3: In Saga, you can call this to navigate to the screen you want
serviceProvider.NavigatorService().navigate(//screenName)

Component's prop doesn't update in React Native with Redux

I need some help with my app and Redux! (Currently, i hate it aha)
So, i have a notification page component which fetch some datas and i need to put the data length into my redux store to put badge on my icon in my tabbar!
My Main Reducer :
import { combineReducers } from "redux";
import NotificationReducer from "./NotificationReducer";
export default function getRootReducer(navReducer) {
return combineReducers({
nav: navReducer,
notificationReducer: NotificationReducer
});
}
My Notification reducer
const initialState = {
NotificationCount: 0
};
export default function notifications(state = initialState, action = {}) {
switch (action.type) {
case 'SET_COUNT' :
console.log('REDUCER NOTIFICATION SET_COUNT',state)
return {
...state,
NotificationCount: action.payload
};
default:
return state;
}
};
My Action :
export function setNotificationCount(count) {
return function (dispatch, getState) {
console.log('Action - setNotificationCount: '+count)
dispatch( {
type: 'SET_COUNT',
payload: count,
});
};
};
My Component :
import React, { Component } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, SectionList, Alert } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { Notification } from '#Components';
import { ORANGE } from '#Theme/colors';
import { NotificationService } from '#Services';
import Style from './style';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as Actions from '#Redux/Actions';
const width = Dimensions.get('window').width
const height = Dimensions.get('window').height
export class NotificationsClass extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
NotificationCount: undefined
};
}
async componentWillMount() {
this.updateNotifications();
}
componentWillReceiveProps(nextProps){
console.log('receive new props',nextProps);
}
async updateNotifications() {
this.props.setNotificationCount(10); <---
let data = await NotificationService.get();
if (data && data.data.length > 0) {
this.setState({ dataSource: data });
console.log(this.props) <-- NotificationCount is undefined
}
}
render() {
if (this.state.dataSource.length > 0) {
return (
<SectionList
stickySectionHeadersEnabled
refreshing
keyExtractor={(item, index) => item.notificationId}
style={Style.container}
sections={this.state.dataSource}
renderItem={({ item }) => this.renderRow(item)}
renderSectionHeader={({ section }) => this.renderSection(section)}
/>
);
} else {
return this.renderEmpty();
}
}
renderRow(data) {
return (
<TouchableOpacity activeOpacity={0.8} key={data.notificationId}>
<Notification data={data} />
</TouchableOpacity>
);
}
}
const Notifications = connect(
state => ({
NotificationCount: state.NotificationCount
}),
dispatch => bindActionCreators(Actions, dispatch)
)(NotificationsClass);
export { Notifications };
(I've removed some useless code)
Top Level :
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return newState || state;
};
#connect(state => ({
nav: state.nav
}))
class AppWithNavigationState extends Component {
render() {
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})}
/>
);
}
}
const store = getStore(navReducer);
export default function NCAP() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
React : 15.6.1
React-Native : 0.46.4
Redux : 3.7.2
React-Redux : 5.0.5
React-Navigation : 1.0.0-beta.11
Node : 6.9.1
So if you've an idea! It will be great :D !
Thanks !
There's three issues.
First, React's re-rendering is almost always asynchronous. In updateNotifications(), you are calling this.props.setNotificationCount(10), but attempting to view/use the props later in that function. Even with the await in there, there's no guarantee that this.props.NotificationCount will have been updated yet.
Second, based on your reducer structure and mapState function, props.NotificationCount will actually never exist. In your getRootReducer() function, you have:
return combineReducers({
nav: navReducer,
notificationReducer: NotificationReducer
});
That means your root state will be state.nav and state.notificationReducer. But, in your mapState function, you have:
state => ({
NotificationCount: state.NotificationCount
}),
state.NotificationCount will never exist, because you didn't use that key name when you called combineReducers.
Third, your notificationReducer actually has a nested value. It's returning {NotificationCount : 0}.
So, the value you actually want is really at state.notificationReducer.NotificationCount. That means your mapState function should actually be:
state => ({
NotificationCount: state.notificationReducer.NotificationCount
}),
If your notificationReducer isn't actually going to store any other values, I'd suggest simplifying it so that it's just storing the number, not the number inside of an object. I'd also suggest removing the word Reducer from your state slice name. That way, you could reference state.notification instead.
For more info, see the Structuring Reducers - Using combineReducers section of the Redux docs, which goes into more detail on how using combineReducers defines your state shape.

#react-native [0.42] - New Navigation with Redux - Cannot map state to props

RN: 0.42
I tried to use the new navigation (that was released) + redux and I am unable to map the initial state of the redux to props, in a screen where the store is passed.
I followed this: https://reactnavigation.org/docs/guides/redux
I have written my custom reducer.
export const types = {
...
}
export const actionCreators = {
authenticate: () => async (dispatch, getState) => {
...
}
}
const initialState = {
auth: {
...
}
}
export const reducer = (state = initialState, action) => {
const {auth} = state
const {type, payload, error} = action
switch (type) {
...
}
return state
}
In index.ios.js I have combined my own custom reducer
import { addNavigationHelpers } from 'react-navigation';
import * from 'appReducer';
const AppNavigator = StackNavigator({
Home: { screen: MyTabNavigator },
});
const navReducer = (state, action) => {
const newState = AppNavigator.router.getStateForAction(action, state);
return (newState ? newState : state)
};
const appReducer = combineReducers({
nav: navReducer,
app: appReducer
});
#connect(state => ({
nav: state.nav,
}))
class AppWithNavigationState extends React.Component {
render() {
return (
<AppNavigator navigation={addNavigationHelpers({
dispatch: this.props.dispatch,
state: this.props.nav,
})} />
);
}
}
const store = createStore(appReducer);
class App extends React.Component {
render() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
}
Inside Home.js, 'mapStateToProps' does not work.
import React, { Component } from 'react'
import { View, Text, StyleSheet } from 'react-native'
import { connect } from 'react-redux'
import { actionCreators } from './appRedux'
const mapStateToProps = (state) => ({
//This is the problem: Here 'state' has no 'auth' object attached to it
auth: state.auth
})
class Home extends Component {
componentWillMount() {
const {dispatch} = this.props
//Dispatch works
dispatch(actionCreators.authenticate('testId', 'testToken'))
}
render() {
const {auth} = this.props
return (
<View style={styles.container}>
<Text>
Welcome {auth['name']}
</Text>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
}
})
export default connect(mapStateToProps)(Home)
Note, the dispatch function is available to fire but the 'state' does not have the reducer 'initialState' attached to it.
Please let me know the correct way to attach the reducer initialState to various Screens in the new RN navigation.
I got it where you are doing wrong, in your index.ios.js change import * from 'appReducer'; to import {reducer} from 'appReducer'; then your current combined reducer function will be like
const appReducer = combineReducers({
nav: navReducer,
app: reducer
});
then in your home.js your mapStateToProps should be like
const mapStateToProps = (state) => ({
//This is the problem: Here 'state' has no 'auth' object attached to it
authState: state.app //remember store only contains reducers as state that you had passed in combineReducer function
})
now use it in your component like
this.props.authState.auth //remember you had wrapped your auth object within initialState object