middlware is not a function in react native - react-native

I am learning React-native and trying to implement redux.
I have used React-redux & React-thunk perform a Async task from the Action. During the implementation, getting an error e.i. "middleware in not a function" when I run. If I comment out middleware and relevant code then everything works fine.
Here is my code below.
index.js
import React, {Component} from 'react';
import ResetUserContainer from "./src/Components/resetUserContainer"
import {Provider} from 'react-redux'
import {createStore,applyMiddleware} from 'redux'
import {thunk} from 'redux-thunk'
import userResetReducer from "./src/Reducers/ResetReducer"
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(userResetReducer);
export default class App extends Component {
render() {
return (
<Provider store = {store}>
<ResetUserContainer/>
</Provider>
);
}
}
ResetUserContainer.js class.
import React, { Component } from "react";
import { StyleSheet, View, ActivityIndicator } from "react-native";
import { connect } from "react-redux"
import userAction from "./Actions/UserAction"
import PropTypes from "prop-types";
class ResetUserContainer extends Components {
constructor(props) {
super(props);
}
componentDidMount() {
this.props.requestToken();
}
render() {
return (
<View style={styles.container}>
<View style={styles.subContainer}>
onPress={this._onPressButton}
containerStyle={{ marginTop: 20 }}
/>
</View>
</View>
<ActivityIndicator
size="large"
color="red"
style={this.props.isFetching ? styles.centering : styles.hideLoader} />
</View>
);
}
_onPressButton = () => {
// this.props.requestToken();
}
}
ResetUserContainer.propTypes = {
requestToken: PropTypes.func.isRequired,
objectMember: PropTypes.object.isRequired
}
const mapStateToProps = state => {
return {
//Here using objectMember, we can access any member of action class
objectMember: state,
//we can use also like this
isFetching: state.isFetching,
errorMsg: state.errorMsg,
displayMsg: state.displayMsg,
token: state.token
}
}
export default connect(mapStateToProps, { userAction })(ResetUserContainer);
types.js
export const TOKEN_REQUEST_PROCESSED = 'TOKEN_REQUEST_PROCESSED';
export const TOKEN_REQUEST_TOKEN_SUCCEEDED= 'TOKEN_REQUEST_TOKEN_SUCCEEDED';
export const TOKEN_REQUEST_FAILED = 'TOKEN_REQUEST_FAILED';
UserAction.js
import AuthInterface from '../../Interfaces/authInterface';
import UserResetModel from '../../Models/userResetModel';
import SpecialUserModel from '../../Models/specialUserModel';
import { TOKEN_REQUEST_PROCESSED, TOKEN_REQUEST_TOKEN_SUCCEEDED, TOKEN_REQUEST_FAILED } from './types';
export const tokenRequestProcess = () => ({ type: TOKEN_REQUEST_PROCESSED });
export const tokenRequestSuccess = (token) => ({ type: TOKEN_REQUEST_TOKEN_SUCCEEDED, payload: token });
export const tokenRequestFailed = (error) => ({ type: TOKEN_REQUEST_FAILED, payload: error });
export const requestToken = () => {
return async dispatch => {
dispatch(tokenRequestProcess);
let specialuser = new SpecialUserModel("", "");
specialuser.Username = "xyz.com";
specialuser.Password = "xyz.password";
AuthInterface.authenticateSpecialUser(specialuser).then((response) => {
let result = new httpResponseModel();
result = response;
if (result.ErrorCode == "OK") {
dispatch(tokenRequestSuccess(result.token_number))
} else {
//Handel all possible failure by error msg
dispatch(tokenRequestFailed(result.error_msg));
}
}, (err) => {
dispatch(tokenRequestFailed(JSON.stringify(err)));
});
}
};
ResetReducer.js
import {
TOKEN_REQUEST_PROCESSED, TOKEN_REQUEST_TOKEN_SUCCEEDED, TOKEN_REQUEST_FAILED
} from './types';
const initialState = {
isFetching: false,
errorMsg: '',
displayMsg: '',
token: ''
};
const resetReducer = (state = initialState, action) => {
switch (action.type) {
case TOKEN_REQUEST_PROCESSED:
return { ...state, isFetching: true };
case TOKEN_REQUEST_TOKEN_SUCCEEDED:
return { ...state, isFetching: false, displayMsg: action.payload }
case TOKEN_REQUEST_FAILED:
return { ...state, isFetching: false, errorMsg: action.payload }
default:
return state;
}
}
export default resetReducer;
package.json
"dependencies": {
"react": "16.5.0",
"react-native": "^0.57.2",
"react-redux": "^5.0.7",
"redux": "^4.0.0",
"redux-thunk": "^2.3.0"
},
"devDependencies": {
"babel-jest": "23.6.0",
"jest": "23.6.0",
"metro-react-native-babel-preset": "0.48.0",
"react-test-renderer": "16.5.0"
},
Please let me know if I am doing something wrong or missing something.
I have googled but couldn't solve, like here in,
Redux thunk in react native
Thanks in advance.

import {thunk} from 'redux-thunk'
Please check this line. You should change that into below.
import thunk from 'redux-thunk'

Related

Why is my navigation ref not ready in React Navigation 6 with Redux?

In React Navigation 6, my research shows that to navigate without a prop I should make a reference and use createNavigationContainerRef. I'm able to pass down the screen name to my dispatch but for some reason when I evaluate the condition with isReady I'm always told it isn't. The code:
App.js:
import React from 'react'
import { NavigationContainer } from '#react-navigation/native'
import 'react-native-gesture-handler'
// Provider
import { Provider as AuthProvider } from './src/context/AuthContext'
// Navigation
import { navigationRef } from './src/navigation/NavRef'
// Screens
import ResolveAuthScreen from './src/screens/ResolveAuthScreen'
const App = () => {
return (
<AuthProvider>
<NavigationContainer ref={navigationRef}>
<ResolveAuthScreen />
</NavigationContainer>
</AuthProvider>
)
}
export default App
ResolveAuthScreen.js:
import React, { useEffect, useContext } from 'react'
// Context
import { Context as AuthContext } from '../context/AuthContext'
const ResolveAuthScreen = () => {
const { tryLocalSignIn } = useContext(AuthContext)
useEffect(() => {
tryLocalSignIn()
}, [])
return null
}
export default ResolveAuthScreen
AuthContext.js (stripped down):
import AsyncStorage from '#react-native-async-storage/async-storage'
// Context
import createContext from './createContext'
// Nav
import * as NavRef from '../navigation/NavRef'
const authReducer = (state, action) => {
switch (action.type) {
case 'signin':
return { errorMessage: '', token: action.payload }
case 'clear_error':
return { ...state, errorMessage: '' }
default:
return state
}
}
const tryLocalSignIn = dispatch => async () => {
const token = await AsyncStorage.getItem('token')
console.log({ token }) // renders token
if (token) {
dispatch({ type: 'signin', payload: token })
NavRef.navigate('TrackListScreen')
} else {
NavRef.navigate('SignUp')
}
}
export const { Provider, Context } = createContext(
authReducer,
{ tryLocalSignIn },
{ token: null, errorMessage: '' },
)
NavRef.js:
import { createNavigationContainerRef } from '#react-navigation/native'
export const navigationRef = createNavigationContainerRef()
export function navigate(name, params) {
console.log({ name, params })
if (navigationRef.isReady()) {
console.log('ready')
console.log({ name, params })
navigationRef.navigate('TrackDetailScreen', { name, params })
} else {
console.log('not ready')
}
}
When I log the token from dispatch I get back the token. When I log the screen I get back TrackListScreen from navigate but whenever it's fired it always returns the console log of not ready.
Docs:
Navigating without the navigation prop
Navigating to a screen in a nested navigator
"dependencies": {
"#react-native-async-storage/async-storage": "~1.15.0",
"#react-navigation/bottom-tabs": "^6.0.9",
"#react-navigation/native": "^6.0.6",
"#react-navigation/native-stack": "^6.2.5",
"axios": "^0.24.0",
"expo": "~43.0.0",
"expo-status-bar": "~1.1.0",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-native": "0.64.2",
"react-native-elements": "^3.4.2",
"react-native-gesture-handler": "~1.10.2",
"react-native-reanimated": "~2.2.0",
"react-native-safe-area-context": "3.3.2",
"react-native-screens": "~3.8.0",
"react-native-web": "0.17.1"
},
Why is my navigate not working after my dispatch or why does the isReady false?
I'm having the same issue. When trying to access the exported navigationRef.isReady() from a redux-saga file, it always returns false. I'm not sure this is a safe approach, nor have I properly tested this, but the following workaround seems to work for me:
App.js
import {setNavigationRef, navigationIsReady} from './NavigationService';
const navigationRef = useNavigationContainerRef();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => {
setNavigationRef(navigationRef);
}}>
...
</NavigationContainer>
);
NavigationService.js
export let navigationRefCopy = undefined;
export function setNavigationRef(navigationRef) {
navigationRefCopy = navigationRef;
}
export function navigationIsReady() {
return navigationRefCopy?.isReady(); // returns true when called in a redux saga file.
}

i can't connect redux with react-native app

I have a problem with redux and react-native because I can't connect the state of the "login" view.
I would like to upload the user data and the token in the store.
In "AppState" View, I manage the actions :
import * as type from "../redux/actionTypes";
import {initialState} from "../redux/initialState";
export default function LoginStateReducer(state = initialState, action) {
switch( action.type ) {
case type.SET_LOGIN_STATE:
return {
...state,
user: action.user,
token: action.token,
isLoading: false,
isLoggedIn: true,
};
case type.SET_LOGOUT_STATE:
return {
...state,
user: null,
token: null,
isLoading: false,
isLoggedIn: false,
};
default:
return state;
}
};
In "AppView", I manage the state :
import React, { useState, useEffect, useReducer, useMemo } from 'react';
import { View, ActivityIndicator } from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
import Login from './login/LoginViewContainer';
import { AuthContext } from './config/Context';
import * as type from '../redux/actionTypes'
import { initialState } from '../redux/initialState';
import LoginStateReducer from './AppState';
import Navigator from './navigation/Navigator';
const App = () => {
const [isLoading, setIsLoading] = useState(true);
const [loginState, dispatch] = useReducer(LoginStateReducer, initialState);
const authContext = useMemo(() => ({
signIn: async(data) => {
setIsLoading(false);
const token = String(data.token);
const user= data.user;
try {
await AsyncStorage .setItem('token', token);
} catch(e) {
console.log(e);
}
dispatch({ type: type.SET_LOGIN_STATE, user: user, token: token });
},
signOut: async() => {
setIsLoading(false);
try {
await AsyncStorage .removeItem('token');
} catch(e) {
console.log(e);
}
dispatch({ type: type.SET_LOGOUT_STATE });
},
}), []);
if( loginState.isLoading ) {
return(
<View style={{flex:1,justifyContent:'center',alignItems:'center'}}>
<ActivityIndicator size="large" />
</View>
);
}
return (
<AuthContext.Provider value={authContext}>
{ loginState.token !== null ? (
<Navigator onNavigationStateChange={() => {}} uriPrefix="/app" />
)
:
<Login />
}
</AuthContext.Provider>
);
}
export default App;
In "AppViewContainer", I connect the reducer with the state :
import { connect } from 'react-redux';
import { compose, lifecycle } from 'recompose';
import { Platform, UIManager } from 'react-native';
import AppView from './AppView';
import LoginStateReducer from './AppState';
export default compose(
connect(
state => ({
user: state.user,
token: state.token,
}),
dispatch => ({
LoginStateReducer: () => dispatch(LoginStateReducer()),
})
),
lifecycle({
componentDidMount() {
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental &&
UIManager.setLayoutAnimationEnabledExperimental(true);
}
this.props.LoginStateReducer();
},
}),
)(AppView);
In the "reducer", I have :
import { combineReducers } from 'redux';
// ## Generator Reducer Imports
import LoginStateReducer from '../modules/AppState';
export default combineReducers({
login: LoginStateReducer,
});
When I call the store once connected, it has not changed and I don't understand why :
"login": {"isLoading": true, "isLoggedIn": false, "token": "", "user": ""}
Can you tell me if i am missing something?
Thank you in advance for your feedback

Connect react-redux to stateless component. Properties inside the function are not updated

Problem:
I have very simple todo app. There is one action - add todo. When I add a task, I simulate sending it to the server using a setTimeout.
When I receive a response from the server, I immediately check to see if there is an error to avoid further action. In stateful component, everything works, and in stateless component it doesn't.
See the code to better understand the problem.
Environment:
"react": "16.8.6",
"react-native": "0.60.5",
"react-redux": "^7.1.1",
"redux": "^4.0.4",
"redux-logger": "^3.0.6",
"redux-thunk": "^2.3.0"
№ 1. Stateful component:
import React, {Component} from 'react';
import {View, Button, ActivityIndicator} from 'react-native';
import {connect} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
class MainScreen extends Component {
todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
sendTodoToServer = async () => {
const todo = this.todoGenerator();
const {addTodo} = this.props;
await addTodo(todo);
// this
const {error} = this.props;
if (error) {
console.log('error', error);
}
};
render() {
const {isLoading} = this.props;
return (
<View>
<Button title="Generate todo" onPress={this.sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
}
}
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreen);
№ 1. Stateful component. Console:
As you can see,
const {error} = this.props;
if (error) {
console.log('error', error);
}
it's work. Okay, let's move on to functional components
№ 2. Stateless component with redux connect:
import React from 'react';
import {ActivityIndicator, Button, View} from 'react-native';
import {connect} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
const MainScreenFC = ({isLoading, addTodo, error}) => {
const todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
const sendTodoToServer = async () => {
const todo = todoGenerator();
await addTodo(todo);
if (error) {
console.log('error', error);
}
};
return (
<View>
<Button title="Generate todo" onPress={sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
};
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreenFC);
№ 2. Stateless component with redux connect. Console:
The error did not display in the console, although it is in the reducer
№ 3. Stateless component with redux HOOKS:
import React from 'react';
import {ActivityIndicator, Button, View} from 'react-native';
import {connect, shallowEqual, useDispatch, useSelector} from 'react-redux';
import {addTodo as addTodoAction} from '../redux/reducer';
const MainScreenReduxHooks = () => {
const todos = useSelector((state: AppState) => state.todos, shallowEqual);
const error = useSelector((state: AppState) => state.error, shallowEqual);
const isLoading = useSelector(
(state: AppState) => state.isLoading,
shallowEqual,
);
const dispatch = useDispatch();
const todoGenerator = () => ({
id: new Date().getTime(),
text: 'Pls help me ' + new Date().getTime(),
});
const sendTodoToServer = async () => {
const todo = todoGenerator();
await dispatch(addTodoAction(todo));
if (error) {
console.log('error', error);
}
};
return (
<View>
<Button title="Generate todo" onPress={sendTodoToServer} />
{isLoading && <ActivityIndicator />}
</View>
);
};
export default connect(
state => ({
todos: state.todos,
error: state.error,
isLoading: state.isLoading,
}),
{
addTodo: addTodoAction,
},
)(MainScreenReduxHooks);
№ 3. Stateless component with redux HOOKS. Console:
It's the same here, as in the second example.
Questions:
Can redux be connected to a stateless component?
How do you make the second and third example work the same way as the first?
Other code:
App.js
import React from 'react';
import {Provider} from 'react-redux';
import {MainScreen, MainScreenFC, MainScreenReduxHooks} from './src/screens';
import store from './src/redux';
const App = () => {
return (
<Provider store={store}>
<MainScreenFC />
</Provider>
);
};
export default App;
store.js:
import {applyMiddleware, createStore} from 'redux';
import thunk from 'redux-thunk';
import logger from 'redux-logger';
import rootReducer from './reducer';
export default createStore(rootReducer, applyMiddleware(thunk, logger));
reducer.js:
const ADD_TODO_REQUEST = 'ADD_TODO_REQUEST';
const ADD_TODO_SUCCESS = 'ADD_TODO_SUCCESS';
const ADD_TODO_FAILURE = 'ADD_TODO_FAILURE';
const initialState = {
todos: [],
isLoading: false,
error: undefined,
};
export const addTodo = data => async dispatch => {
dispatch({
type: ADD_TODO_REQUEST,
payload: {
isLoading: true,
},
});
try {
const todo = await new Promise((resolve, reject) => {
setTimeout(() => {
reject('Ooops, error');
}, 3000);
});
dispatch({
type: ADD_TODO_SUCCESS,
payload: {
todo,
isLoading: false,
},
});
} catch (e) {
dispatch({
type: ADD_TODO_FAILURE,
payload: {
isLoading: false,
error: e,
},
});
}
};
export default function(state = initialState, {type, payload}) {
switch (type) {
case ADD_TODO_REQUEST: {
return {
...state,
isLoading: true,
};
}
case ADD_TODO_SUCCESS: {
return {
...state,
isLoading: false,
todos: [...state.todos, payload.todo],
};
}
case ADD_TODO_FAILURE: {
return {
...state,
isLoading: false,
error: payload,
};
}
default:
return state;
}
}
I think the problem with your code is that you try to wait for a response in the component, it's a bad idea for both stateful and stateless components. What would I recommend you to do, is to handle error somewhere in your middleware (redux-thunk, redux-saga, etc). In this case, your component should just represent data, and if you have to display an error, just take it from props, I believe it is stored somewhere in redux.
In any way, stateless components shouldn't be an async function, because the result of an async function is a promise but not a component. There are some libraries like async-reactor, but personally, I prefer to go with another approach.
If you tell me your use case, what would you like to do once you got an error, I'll give you a more useful answer.
Update:
export const addTodo = data => dispatch => {
// tell your application that request is sending,
// so you can handle it in UI (show a progress indicator)
dispatch({
type: ADD_TODO_REQUEST,
payload: data
});
try {
const response = await createTodo(data);
dispatch({
type: ADD_TODO_SUCCESS,
payload: response
});
// here you can dispatch navigation action as well
} catch (error) {
dispatch({
type: ADD_TODO_FAILURE,
error
});
// and here you can dispatch action with a toast
// to notify users that something went wrong
}
};

React Native and Redux Persist not saving state to persisted storage

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)

Trying to integrate Redux into React Navigation

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