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)
Related
I created a sample React Native app to see if I can get Redux-persist working. However my React Native app with Redux Persist is not saving state to persisted storage.
Every time i change the toggle to 'true' and then reload the app, the state is not persisted and goes back to null.
How can I get 'True' to stay persisted when I refresh the app.
Here is my code:
index.js:
import {AppRegistry} from 'react-native';
import {name as appName} from './app.json';
import React, {Component} from 'react';
import { Provider } from "react-redux";
import { store, persistor } from "./Store/index";
import { PersistGate } from 'redux-persist/integration/react'
import App from './App.js';
class ReduxPersistTest extends Component {
render() {
return (
<Provider store={store}>
<PersistGate persistor={persistor} loading={null}>
<App />
</PersistGate>
</Provider>
);
}
}
AppRegistry.registerComponent('ReduxPersistTest', () => ReduxPersistTest);
store/index.js:
import { createStore, applyMiddleware, compose } from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';
import thunk from 'redux-thunk';
import toggle from "../Reducers/rootReducer.js";
import { AsyncStorage } from "react-native";
import {persistStore, persistReducer, persistCombineReducers} from "redux-persist";
import storage from 'redux-persist/lib/storage'
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
const togglePersistConfig = {
key: 'toggle',
storage: AsyncStorage
};
const middleware = [thunk];
const persistConfig = {
key: 'root',
storage: AsyncStorage,
debug: true,
whitelist: ['toggle']
}
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const reducers = { toggle: persistReducer(togglePersistConfig, toggle) };
const persistedReducer = persistCombineReducers(persistConfig, reducers);
export const store = createStore(
persistedReducer, composeEnhancers(applyMiddleware(...middleware))
);
export const persistor = persistStore(store);
Reducer.js
The issue might be found here in my reducer...
import { ADD_TOGGLE } from "../Constants/action-types";
import { combineReducers } from 'redux';
const initialState = {
toggle: false,
};
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TOGGLE:
console.log(action.payload.toggle);
console.log(action.payload);
return {
...state, toggle: {
toggle: action.payload.toggle,
}};
default:
return state;
}
};
export default rootReducer;
Actions/index.js
import { ADD_TOGGLE } from "../Constants/action-types";
export const addToggle = toggle => ({ type: ADD_TOGGLE, payload: toggle });
Constants/action-Types.js
export const ADD_TOGGLE = "ADD_TOGGLE";
And my components:
App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Copy from './Components/Copy/Copy.js';
import CopyToggle from './Components/Copy/CopyToggle.js';
/*
Redux imports
*/
import { connect } from "react-redux";
import { addToggle } from "./Actions/index";
/*
Redux constants
*/
const mapDispatchToProps = dispatch => {
return {
addToggle: toggle => dispatch(addToggle(toggle))
};
};
//Styles
const styles = StyleSheet.create({
textHeader: {
textAlign: 'center',
marginBottom: 10,
marginTop: 100,
},
});
class App extends Component {
constructor(props) {
super(props);
this.state = {
toggle: false,
};
}
componentWillMount() {
const { toggle } = this.state;
this.props.addToggle({ toggle });
}
render() {
return (
<View>
<Text style={styles.textHeader}>Welcome to React Native!</Text>
<Copy />
<CopyToggle />
</View>
)
}
}
export default connect(null, mapDispatchToProps)(App);
Copy.JS (toggle UI to change the toggle value from 'true' to 'false'
import React, { Component } from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';
import { compose } from 'react-compose';
import { Switch } from 'react-native-switch';
import { connect } from "react-redux";
import { addToggle } from "../../Actions/index";
const mapDispatchToProps = dispatch => {
console.log('mapDispatchToProps hit');
return {
addToggle: toggle => dispatch(addToggle(toggle))
};
};
class Copy extends Component {
constructor(props) {
super(props);
this.state = {
toggle: false,
};
this.addtoggle = this.addtoggle.bind(this);
}
addtoggle(val) {
this.setState({
toggle: val,
}, function () {
const { toggle } = this.state;
this.props.addToggle({ toggle });
});
}
render() {
return (
<View>
<Text>Test redux persist</Text>
<Switch
value={ this.state.toggle }
onValueChange={(val) => this.addtoggle(val)}
/>
</View>
);
}
}
export default connect(null, mapDispatchToProps)(Copy);
CopyToggle.js (outputs the boolean value of the toggle)
import React, { Component } from 'react';
/*
Redux imports
*/
import { connect } from "react-redux";
import { addToggle } from "../../Actions/index";
/*
Native base and react native
*/
import { StyleSheet, View, Text } from 'react-native';
/*
Redux constants
*/
const mapDispatchToProps = dispatch => {
return {
addToggle: toggle => dispatch(addToggle(toggle))
};
};
const mapStateToProps = state => {
return { toggle: state.toggle.toggle };
};
// Custom Styles
const styles = StyleSheet.create({
textHeader: {
color: '#000',
},
});
//class
class CopyToggle extends Component {
constructor(props) {
super(props);
this.state = {
purchase: false,
};
this.toggleDisplay = this.toggleDisplay.bind(this);
}
componentWillMount() {
const { toggle } = this.state;
this.props.addToggle({ toggle });
}
//display to output bollean value
toggleDisplay() {
let toggleState;
if (this.props.toggle === false) {
toggleState = 'false'
}
else if (this.props.toggle === true) {
toggleState = 'true'
}
return (
<Text>{toggleState}</Text>
)
}
//render
render() {
return (
<View>
{this.toggleDisplay()}
</View>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(CopyToggle);
It would be greatly appreciated id someone with knowledge on Redux persist can review and hopefully point out my issue.
THanks!
You need to use persistCombineReducers if you want to persist just part of your store, so I would come up with something similar to this:
import { persistCombineReducers, persistReducer } from 'redux-persist';
import toggle from './Reducer.js';
const togglePersistConfig = {
key: 'toggle',
storage: AsyncStorage
};
const reducers = {
toggle: persistReducer(togglePersistConfig, toggle),
// ...other reducers
}
const persistConfig = {
key: 'root',
storage: AsyncStorage,
debug: true,
whitelist: ['toggle']
};
const persistedReducer = persistCombineReducers(persistConfig, reducers);
export const store = createStore( persistedReducer, composeEnhancers(applyMiddleware(...middleware)) );
You have to add the value you want to persist in the storage object:
AsyncStorage.setItem('toggle': this.state.toggle)
index.js
import React from 'react';
import {
AppRegistry
} from 'react-native'
import App from './App';
import { YellowBox } from 'react-native';
YellowBox.ignoreWarnings(['Warning: isMounted(...) is deprecated', 'Module RCTImageLoader']);
AppRegistry.registerComponent('mhagora', () => App);
App.js
import React, { Component } from 'react';
import { Provider } from "react-redux";
import store from './app/store';
import { StyleProvider, getTheme } from "native-base";
import Setup from "./app/setup";
import variables from "./app/theme/variables/commonColor";
export default class App extends Component {
render() {
return (
<Provider store={store}>
<StyleProvider style={getTheme(variables)}>
<Setup />
</StyleProvider>
</Provider>
);
}
}
./app/setup.js
import React, { Component } from "react";
import axios from "axios/index";
import Config from "./config";
import { Root } from "native-base";
import AppNavigator from "./routes";
axios.defaults.baseURL = Config.API_BASE_URL;
axios.defaults.headers.common['Content-Type'] = Config.API_ACCEPT;
axios.defaults.headers.common['Accept'] = Config.API_ACCEPT;
axios.defaults.headers.common['secret'] = Config.API_SECRET;
export default class Setup extends Component {
render() {
return (
<Root>
<AppNavigator />
</Root>
);
}
}
./app/store/index.js
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { createLogger } from 'redux-logger';
import reducers from '../reducers';
const logger = createLogger();
export default createStore(reducers, compose(applyMiddleware(thunk, logger)));
./app/actions/index.js
import { APP_LOADING, APP_LOADED } from '../actionTypes';
export function appLoading() {
return (dispatch) => {
dispatch({type: APP_LOADING});
}
}
export function appLoaded() {
return (dispatch) => {
dispatch({type: APP_LOADED});
}
}
./app/actions/user.js
import { USER_LOADING, USER_LOADED, USER_FAILED, APP_LOADING, APP_LOADED } from "../actionTypes";
import axios from 'axios';
import Config from '../config';
export function userLogin(username, password) {
return (dispatch) => {
dispatch({type: USER_LOADING});
axios
.post("oauth/token", {
username: username,
password: password,
client_id: Config.API_CLIENT_ID,
client_secret: Config.API_CLIENT_SECRET,
grant_type: 'password',
}, {
headers: {}
})
.then(response => {
dispatch({
type: USER_LOADED,
data: response.data
});
})
.catch(err => {
dispatch({ type: USER_FAILED, error: err.response.data.message });
alert(err.response.data.message);
});
};
}
./app/reducers/index.js
import appReducer from './appReducer';
import userReducer from './userReducer';
import { combineReducers } from "redux";
const rootReducer = combineReducers({
appReducer,
userReducer
});
export default rootReducer;
./app/reducers/userReducer.js
import { USER_LOADING, USER_LOADED, USER_FAILED } from '../actionTypes';
const initialState = {
username: "",
password: "",
user: {}
};
export default userReducer = (state = initialState, action) => {
switch (action.type) {
case USER_LOADING:
return Object.assign({}, state, {
loading: true,
user: {},
});
case USER_LOADED:
return Object.assign({}, state, {
loading: false,
user: action.data
});
case USER_FAILED:
return Object.assign({}, state, {
loading: false,
});
default:
return state
}
}
./app/reducers/appReducer.js
import { APP_LOADING, APP_LOADED } from "../actionTypes";
const initialState = {
loading: true,
};
export default appReducer = (state = initialState, action) => {
switch (action.type) {
case APP_LOADING:
return Object.assign({}, state, {
loading: true
});
case APP_LOADED:
return Object.assign({}, state, {
loading: false
});
default:
return state;
}
};
./app/screens/home.js
'use strict';
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { SkypeIndicator } from 'react-native-indicators';
import * as Actions from '../actions/index';
import { Container, Header, Title, Content, Footer, FooterTab, Button, Left, Right, Body, Icon, Text, View } from 'native-base';
class HomeScreen extends Component {
componentDidMount() {
/** HERE, the apps should show a loading page forever but it didn't **/
// setTimeout( _ => {
// this.props.appLoaded();
// }, 2000);
}
render() {
if (this.props.loading) {
return (
<SkypeIndicator />
);
} else {
return (
<Container>
<Header>
</Header>
<Body>
<Button
onPress={() =>
this.props.navigation.navigate('LoginScreen')
}><Text>Login now</Text></Button>
<Text>Hello</Text>
</Body>
</Container>
);
}
}
}
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.loading,
user: state.user,
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/homeScreen.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen);
./app/screens/loginScreen
'use strict';
import React, { Component } from 'react';
import { StyleSheet } from 'react-native';
import { SkypeIndicator } from 'react-native-indicators';
import { bindActionCreators } from "redux";
import { connect } from "react-redux";
import { Body, Button, Container, Content, Header, Icon, Left, Text, Title, View } from "native-base";
import t from 'tcomb-form-native';
import { LoginUserModel, LoginUserModelOption } from "../models/UserModel";
import styles from '../styles';
import LoadingButton from 'react-native-loading-button';
import * as UserActions from '../actions/user';
const Form = t.form.Form;
const ps = StyleSheet.create({
...styles,
container: {
justifyContent: 'center',
marginTop: 50,
padding: 20
},
});
class LoginScreen extends Component {
constructor(props) {
super(props);
}
onSubmitHandler = () => {
const value = this._form.getValue();
if(value) {
this.props.userLogin(value.username, value.password);
}
};
render() {
return (
<Container>
<Header>
<Left>
<Button transparent onPress={() => this.props.navigation.goBack()}>
<Icon name="arrow-back"/>
</Button>
</Left>
<Body>
<Title>Headers</Title>
</Body>
</Header>
<Content padder>
<View style={ps.container}>
<Form ref={c => this._form = c} type={LoginUserModel} options={LoginUserModelOption} />
<LoadingButton
block
onPress={this.onSubmitHandler.bind(this)}
isLoading={this.props.loading}
style={{ justifyContent: 'center' }}
><Icon name="checkmark"/><Text>Login Now</Text></LoadingButton>
</View>
</Content>
</Container>
);
}
}
// The function takes data from the app current state,
// and insert/links it into the props of our component.
// This function makes Redux know that this component needs to be passed a piece of the state
function mapStateToProps(state, props) {
return {
loading: state.loading,
user: state.user,
}
}
// Doing this merges our actions into the component’s props,
// while wrapping them in dispatch() so that they immediately dispatch an Action.
// Just by doing this, we will have access to the actions defined in out actions file (action/homeScreen.js)
function mapDispatchToProps(dispatch) {
return bindActionCreators(UserActions, dispatch);
}
//Connect everything
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
the homeScreen should result in a forever loading page but it didn't
the loginScreen button should automatically loading when pressing, but it didn't
new to react-native, i have tried to set/play with the state/props but it just seems like not changing/connected, i also have another page trying to check the state is synced but results is like always get the fresh state, as my understanding the state is something like GLOBAL variable accessible in any component connect to redux
MY QUESTION IS
1. is the react-native/redux/redux-thunk setup correctly? if not, where is the error
2. is the state/props is global accessible in any component that connect with redux
3. if statement 2 is correct, what the different between state/props? this.state and this.props
4. i don't really understand the promise work, how can we handle / wait untill the api call complete(success/error) before move to next step/flow, i use php a lot and my logic is stuck at each function should return something then depends on the results process to next function...and then...
your answer / precious time spend for reading this question is appreciated, thank you
created a github for easy to reproduce/test
https://github.com/weiloon1234/react-native-test
I'am using react navigation with redux and after redux integration, i got some errors on drawer close.
import React from "react";
. . .
import { NavigationActions } from "react-navigation";
import { StackNavigator, DrawerNavigator } from 'react-navigation';
import { addListener } from "./components/common/utils";
import Dashboard from './components/pages/Dashboard';
. . .
const MainNavigator = StackNavigator({
Dashboard : {
screen : Dashboard,
},
. . .
})
export const AppNavigator = DrawerNavigator(
{
Main: { screen: MainNavigator }
}, {
contentComponent: Menu,
drawerWidth: 300,
headerMode: 'screen',
drawerPosition: 'left',
drawerOpenRoute: 'DrawerOpen',
drawerCloseRoute: 'DrawerClose',
drawerToggleRoute: 'DrawerToggle',
}
)
class AppWithNavigationState extends React.Component {
constructor (props) {
super(props)
this.onBackPress = this.onBackPress.bind(this)
}
componentDidMount () {
BackHandler.addEventListener('hardwareBackPress', this.onBackPress)
}
componentWillUnmount () {
BackHandler.removeEventListener('hardwareBackPress', this.onBackPress)
}
onBackPress () {
...
}
render() {
const { dispatch, nav } = this.props;
return (
<AppNavigator
navigation={{
dispatch,
state: nav,
addListener,
}}
/>
);
}
}
const mapStateToProps = state => ({
nav: state.nav,
});
AppWithNavigationState.propTypes = {
dispatch: PropTypes.func.isRequired,
nav: PropTypes.object.isRequired,
};
export default connect(mapStateToProps)(AppWithNavigationState);
Here is my reducer:
import { fromJS } from 'immutable';
import { NavigationActions } from "react-navigation";
import { combineReducers } from "redux";
import { AppNavigator } from "../../App";
import {...} from './constants';
import { ToastAndroid } from 'react-native';
const mainAction = AppNavigator.router.getActionForPathAndParams('Main');
const initialNavState = AppNavigator.router.getStateForAction(mainAction);
function nav(state = initialNavState, action) {
let nextState;
switch (action.type) {
case 'Reports':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.back(),
state
);
break;
default:
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}
return nextState || state;
}
const initialState = fromJS({
isLoading: true,
...
});
function store(state = initialState, action) {
switch (action.type) {
case SET_IS_LOADING:
return state.set('isLoading', action.value);
...
default:
return state;
}
}
const AppReducer = combineReducers({
nav,
store,
});
export default AppReducer;
and file i call DraweOpen:
import React from "react";
import PropTypes from "prop-types";
import { TouchableOpacity } from "react-native";
import { createStructuredSelector } from 'reselect';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Icon from "react-native-vector-icons/dist/MaterialIcons";
import { DrawerBurger } from "../common/styles";
import { navigate } from "../store/actions";
const drawerButton = (props) => (
<DrawerBurger>
<TouchableOpacity
onPress={() => props.navigate("DrawerOpen")}
>
<Icon name="menu" size={30} color="white" />
</TouchableOpacity>
</DrawerBurger>
);
drawerButton.propTypes = {
navigate: PropTypes.func.isRequired,
};
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = dispatch => (
(
bindActionCreators({
navigate,
}, dispatch)
)
);
export default connect(mapStateToProps, mapDispatchToProps)(drawerButton);
and i call drawerButton component on navigation options like:
...
class Dashboard extends Component {
static navigationOptions = () => ({
headerTitle: <Header dashboard />,
headerStyle: { backgroundColor: '#2c4e0f' },
headerLeft: <DrawerButton />,
});
...
I followed instructions on reactnavigation.org, also read some example code to build navigator.
Actually there was no error before redux integration and the navigator structure was same except BackHandling.
Here is my actions.js:
import { NavigationActions } from "react-navigation";
import {...} from './constants';
...
export const navigate = routeName => NavigationActions.navigate({ routeName });
My environment is:
react-navigation: 1.5.11
react-native: 0.53.0
react-navigation-redux-helpers: 1.0.5
react-redux: 5.0.7
redux: 3.7.2
node: 8.9.4
npm: 5.6.0
Thank for your help.
According to the redux integration docs, it seems you've missed one step.
You need to add addNavigationHelpers from React Navigation
Usage
import {addNavigationHelpers} from 'react-navigation';
<AppNavigator navigation={addNavigationHelpers({
dispatch,
state: nav,
addListener,
})} />
I'm trying to integrate Redux, into an existing React Native application who use React Navigation.
The dependencies in package.json file are:
"react": "^16.0.0",
"react-native": "^0.51.0",
"react-native-smart-splash-screen": "^2.3.5",
"react-navigation": "^1.0.0-rc.2",
"react-navigation-redux-helpers": "^1.0.0",
My code are:
./App.js
import React, { Component } from "react"
import { AppRegistry, StyleSheet, View } from "react-native"
import { Provider } from "react-redux"
import { createStore } from "redux"
import SplashScreen from "react-native-smart-splash-screen"
import AppReducer from "./reducers/AppReducer"
import AppWithNavigationState from "./navigators/AppNavigator"
class App extends Component {
store = createStore(AppReducer);
componentWillMount() {
SplashScreen.close({
animationType: SplashScreen.animationType.scale,
duration: 850,
delay: 500,
});
}
render() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
)
}
}
AppRegistry.registerComponent("App", () => App)
export default App
./navigators/AppNavigator.js
import { addNavigationHelpers, StackNavigator } from "react-navigation"
import { connect } from "react-redux"
import StackLoading from "../screens/app/StackLoading"
import StackAuth from "../screens/auth/StackAuth"
export const AppNavigator = StackNavigator({
Login: { screen: StackAuth },
Main: { screen: StackLoading },
},
{
headerMode: 'screen',
header: null,
title: 'MyApp',
initialRouteName: 'Login',
})
const AppWithNavigationState = ({ dispatch, nav }) => (
<AppNavigator
navigation={addNavigationHelpers({ dispatch, state: nav })}
/>
);
const mapStateToProps = state => ({
nav: state.nav,
})
export default connect(mapStateToProps)(AppWithNavigationState)
./reducers/AppReducer.js
import { combineReducers } from 'redux';
import NavReducer from './NavReducer';
const AppReducer = combineReducers({
nav: NavReducer,
});
export default AppReducer;
./reducers/AppReducer.js
import { combineReducers } from 'redux';
import { NavigationActions } from 'react-navigation';
import { AppNavigator } from '../navigators/AppNavigator';
const router = AppNavigator.router;
const mainNavAction = AppNavigator.router.getActionForPathAndParams('Main')
const mainNavState = AppNavigator.router.getStateForAction(mainNavAction);
const loginNavAction = AppNavigator.router.getActionForPathAndParams('Login')
const initialNavState = AppNavigator.router.getStateForAction(loginNavAction, mainNavState)
function nav(state = initialNavState, action) {
let nextState;
switch (action.type) {
case 'Login':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.back(),
state
);
break;
case 'Logout':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Login' }),
state
);
break;
default:
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
}
const initialAuthState = { isLoggedIn: false };
function auth(state = initialAuthState, action) {
switch (action.type) {
case 'Login':
return { ...state, isLoggedIn: true };
case 'Logout':
return { ...state, isLoggedIn: false };
default:
return state;
}
}
const AppReducer = combineReducers({
nav,
auth,
});
export default AppReducer;
I have used various approaches following as many guides. The error that I continue to have is this:
ReactNativeJS: undefined is not an object (evaluating
'state.routes[childIndex]')
ReactNativeJS: Module AppRegistry is not a
registered callable module (calling runApplication)
Please help me :\
PrimaryNavigator is my top level navigator.
I am using some helper functions that disables pushing the same component multiple times to the stack which is a common problem in react-navigation.
My helper functions respectively ;
function hasProp(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
// Gets the current route name
function getCurrentRouteName(nav) {
if (!hasProp(nav, 'index') || !hasProp(nav, 'routes')) return nav.routeName;
return getCurrentRouteName(nav.routes[nav.index]);
}
function getActionRouteName(action) {
const hasNestedAction = Boolean(
hasProp(action, 'action') && hasProp(action, 'type') && typeof action.action !== 'undefined',
);
const nestedActionWillNavigate = Boolean(hasNestedAction && action.action.type === NavigationActions.NAVIGATE);
if (hasNestedAction && nestedActionWillNavigate) {
return getActionRouteName(action.action);
}
return action.routeName;
}
And then setting the nav reducer :
const initialState = PrimaryNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'StartingScreen' })
);
const navReducer = (state = initialState, action) => {
const { type } = action;
if (type === NavigationActions.NAVIGATE) {
// Return current state if no routes have changed
if (getActionRouteName(action) === getCurrentRouteName(state)) {
return state;
}
}
// Else return new navigation state or the current state
return PrimaryNavigator.router.getStateForAction(action, state) || state;
}
Finally, you can combine navReducer inside your combineReducers function.
Please let me know if my answer does not help your case
AppRegistry.registerComponent("App", () => App) should happen in index.ios.js or index.android.js
your index.ios.js file should look like
import App from './src/App';
import { AppRegistry } from 'react-native';
AppRegistry.registerComponent('your_app_name', () => App);
I have followed this great tutorial which is Tab Bar with three tabs using redux. Everything works great. Now I am trying to nest this Tab Bar inside Stack Navigator but I have the following error:
I am new to Redux and really cannot find where is the problem. Here is my code:
StackNav.js
import React from 'react';
import { connect } from 'react-redux';
import { addNavigationHelpers } from 'react-navigation';
import { RootNav } from './../navigationConfiguration';
const mapStateToProps = (state) => {
return { navigationState: state.nav };
};
class StackNav extends React.Component {
render() {
const { dispatch, navigationState } = this.props;
return (
<RootNav
navigation={
addNavigationHelpers({
dispatch,
state: navigationState,
})
}
/>
);
}
}
export default connect(mapStateToProps)(StackNav);
StackNav's navigationConfiguration.js
import { StackNavigator } from 'react-navigation';
import TabBarNavigation from './../tabBar/views/TabBarNavigation';
import Welcome from './../../Screens/Register/Welcome.js';
const routeConfiguration = {
Welcome: { screen: Welcome },
Home: { screen: TabBarNavigation },
};
const stackNavigatorConfiguration = {
initialRouteName: 'Welcome',
headerMode: 'screen',
navigationOptions: {
header: { visible: false }
}
};
export const RootNav = StackNavigator(routeConfiguration, stackNavigatorConfiguration);
Reducers
import { combineReducers } from 'redux';
// Navigation
import { AppNavigator } from './../stackNav/navigationConfiguration';
import { NavigatorTabOne } from './../tabOne/navigationConfiguration';
import { NavigatorTabTwo } from './../tabTwo/navigationConfiguration';
import { NavigatorTabThree } from './../tabThree/navigationConfiguration';
export default combineReducers({
nav: (state, action) => AppNavigator.router.getStateForAction(action, state),
tabOne: (state, action) => NavigatorTabOne.router.getStateForAction(action, state),
tabTwo: (state, action) => NavigatorTabTwo.router.getStateForAction(action, state),
tabThree: (state, action) => NavigatorTabThree.router.getStateForAction(action, state),
});
I also tried with this reducer instead nav: above
import { AppNavigator } from './../stackNav/navigationConfiguration';
const initialState = AppNavigator.router.getStateForAction(AppNavigator.router.getActionForPathAndParams('Welcome'));
export const navReducer = (state = initialState, action) => {
const nextState = AppNavigator.router.getStateForAction(action, state);
return nextState || state;
};
Start point of the app:
import React from 'react';
import {
AppRegistry,
Text
} from 'react-native';
import { Provider } from 'react-redux';
import StackNav from './../App/stackNav/views/StackNav';
import store from './store';
Text.defaultProps.allowFontScaling = false;
class App extends React.Component {
render() {
return (
<Provider store={store}>
<StackNav />
</Provider>
);
}
}
AppRegistry.registerComponent('MyApp', () => App);
I will appreciate any help. Thank you in advanced!
well, you're importing AppNavigator when you shoould be importing { RootNav } in reducer index
I don't have a direct answer to your question, but I can offer an example of how to nest Tab Navigators in Stack Navigators in this tutorial - https://developerlife.com/2017/04/15/navigation-and-styling-with-react-native/
Here's the JS class (from the tutorial and it's GitHub repo) that sets up the navigators and nesting - https://github.com/r3bl-alliance/react-native-weather/blob/master/app/Router.js