Issue
The store is not persisted or somehow is reset when relaunching the application
Expected behaviour
The app has basically just a login form in which some redux actions are triggered. Like for example both username and password are saved into the store.
After inspecting with Reactotron, the store is actually evolved as expected, both username and password, along with other fields are updated.
Tech stack
React Native app (not Expo), with Redux
package.json
"dependencies": {
"react": "16.6.1",
"react-native": "0.57.7",
"react-native-gesture-handler": "^1.0.12",
"react-native-i18n": "^2.0.15",
"react-navigation": "^3.0.8",
"react-redux": "^6.0.0",
"redux": "^4.0.1",
"redux-persist": "^5.10.0"
},
Application status
I have create a simple ReactNative app where the root component is:
App.js
import React, {Component} from 'react';
import { Provider } from 'react-redux';
import LoadingIndicator from '@components/Visual/LoadingIndicator/';
import MainNavigator from '@components/Visual/MainNavigator'
import {checkUserState} from '@actions/GlobalActions'
import {store, persistor} from '@components/Storage/StorageConfigurationBuilder'
import { PersistGate } from 'redux-persist/integration/react'
export default class App extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
store.dispatch(checkUserState())
}
render() {
return (
<Provider store={store}>
<PersistGate loading={<LoadingIndicator />} persistor={persistor}>
<MainNavigator initialRoute={this.props.initialRoute} />
</PersistGate>
</Provider>
);
}
}
@components
and @actions
in the imports are resolved by the babel plugin "module-resolver"
.babelrc
...
["module-resolver", {
"root": ["."],
"alias": {
"^@constants/(.+)": "./constants/\\1",
"@i18n": "./i18n/index.js",
"^@components/(.+)": "./components/\\1",
"^@screens/(.+)": "./screens/\\1",
"^@reducers/(.+)": "./reducers/\\1",
"^@actions/(.+)": "./actions/\\1",
}
}]
...
the StorageConfigurationBuilder imported in the route app which provides both the store and the persistor is simply:
StorageConfigurationBuilder.js
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage/index.native'
import AllReducers from '@reducers/AllReducers'
//import Reactotron from '../../ReactronConfig'
const persistConfig = {
key: 'root',
storage
}
const persistedReducer = persistReducer(persistConfig, AllReducers)
export const store = createStore(persistedReducer)
export const persistor = persistStore(store)
Finally the view component that is being rendered, which is separated into two parts
index.js
import React, {Component} from 'react'
import { connect } from 'react-redux';
import LoginView from './LoginView';
import {loginFetch, loginFetchError, loginFetchSuccess} from '@actions/UserActions'
import ApiConfiguration, { getApiBaseUri } from '@constants/ApiConfiguration'
class LoginController extends Component {
constructor(props) {
super(props)
}
doLogin() {
this.props.dispatch(loginFetch());
fetch(getApiBaseUri() + ApiConfiguration.endpoints.authentication.path, {
...ApiConfiguration.headers,
method: ApiConfiguration.endpoints.authentication.method,
body: JSON.stringify({
email: this.props.email,
password: this.props.password
})
}).then((response) => {
return response.json()
}).then((response) => {
this.props.dispatch(loginFetchSuccess(response))
}).catch((e) => {
this.props.dispatch(loginFetchError(e))
})
}
render() {
return <LoginView {...this.props} login={this.doLogin.bind(this)} />;
}
}
const mapStateToProps = (state) => {
const { user } = state
return { user }
};
export default connect(mapStateToProps)(LoginController);
The view itself <LoginView />
has nothing special about it
LoginView.js
import React from 'react';
import {StyleSheet, SafeAreaView, View, TextInput, Text, TouchableOpacity} from 'react-native';
import {changeEmail, changePassword} from '@actions/UserActions'
import I18n from '@i18n';
import Colors from '@constants/Colors';
export default props => {
return (<SafeAreaView style={styles.container}>
<View style={styles.loginForm}>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>{I18n.t('login.email_label')}</Text>
<TextInput
value={props.user.email}
onChangeText={value => props.dispatch(changeEmail(value))}
placeholder={I18n.t('login.email_placeholder')}
style={styles.field}
textContentType={"username"}
returnKeyType={"next"}
underlineColorAndroid={"transparent"}></TextInput>
</View>
<View style={styles.fieldContainer}>
<Text style={styles.fieldLabel}>{I18n.t('login.password_label')}</Text>
<TextInput
value={props.user.password}
onChangeText={value => props.dispatch(changePassword(value))}
style={styles.field}
placeholder={I18n.t('login.password_placeholder')}
textContentType={"password"}
underlineColorAndroid={"transparent"}
secureTextEntry={true}></TextInput>
</View>
<View style={styles.panelFooter}>
<TouchableOpacity onPress={() => props.login()}>
<Text>
{I18n.t('login.login_button')}
</Text>
</TouchableOpacity>
</View>
</View>
</SafeAreaView>);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.backgroundColor,
justifyContent: 'center'
},
loginForm: {
marginLeft: 20,
marginRight: 20,
justifyContent: 'space-around',
borderWidth: 1,
borderColor: Colors.borderColor,
borderRadius: 5
},
panelFooter: {
flexDirection: 'row',
justifyContent: 'flex-end',
backgroundColor: '#e5e5e5',
height: 44,
paddingRight: 20,
paddingTop: 13,
marginTop: 35
},
fieldContainer: {
height: 50,
flexDirection: 'column',
marginLeft: 20,
marginRight: 20,
marginTop: 25
},
fieldLabel: {
fontSize: 13,
fontWeight: "600",
marginBottom: 4,
marginLeft: 10
},
field: {
height: 44,
borderWidth: 1,
borderColor: Colors.borderColor,
borderRadius: 22,
fontSize: 18,
paddingLeft: 22,
paddingRight: 10
}
})
So I added a simple reducer / action and attached it to my app, the same App described above. I got this code from another standalone project I created earlier which was working fine.
This part, the FriendReducer continued to work fine while my reducers described above were still not working.
Finally I added the following case in the reducer to restore my application state. For some reason it was not doing it on its own.
case 'persist/REHYDRATE':
//rehydrating userStore to app
newState = { ...action.payload.userStore }
return newState