Reputation: 193
I've tried for a week or more to make my state persisted in a react-native Android application but after rehydrating the state is always with initial values. If I check ASyncStorage contents or the state with Redux devtools, it is the initial states:
{
"status":"{\"actions\":[]}",
"list":"{\"actions\":[]}",
"map":"{\"site_ids\":{},\"alarmCounts\":[],\"geoJSON\":{}}",
"_persist":"{\"version\":-1,\"rehydrated\":true}"
}
I also get error Unexpected key "_persist" ...
when I use redux-reset and couldn't find any solution from Google.
If I run store.flush()
or store.purge()
, I don't see any changes in ASyncStorage.
My configureStore file is as follows. I think the problem is somewhere in here. Any help?
import { createStore } from "redux";
import { composeWithDevTools } from 'redux-devtools-extension';
import { persistStore, persistCombineReducers } from "redux-persist";
import reduxReset from 'redux-reset';
import { AsyncStorage } from 'react-native'
import userReducer from '../reducers/user';
import statusReducer from '../reducers/status';
import listReducer from '../reducers/list';
import mapReducer from '../reducers/map';
function reset(): void {
console.log('Reseting state');
store.dispatch({
type: 'RESET'
});
// persistor.purge();
};
function flush() {
console.log('FLUSH');
persistor.flush();
}
const enhancer = composeWithDevTools(
reduxReset()
);
const appReducer = persistCombineReducers(
{ // cfg
key: 'primary',
storage: AsyncStorage,
blacklist: ['user'],
throttle: 1000
},
{
user: userReducer,
status: statusReducer,
list: listReducer,
map: mapReducer
}
);
const rootReducer = (state, action) => {
return appReducer(state, action);
}
const store = createStore(
rootReducer,
enhancer
);
const persistor = persistStore(store, null);
export default { store, persistor, reset, flush };
I use [email protected], [email protected], [email protected]
Upvotes: 2
Views: 5458
Reputation: 4841
Specify stateReconciler with value hardSet
import hardSet from "redux-persist/es/stateReconciler/hardSet";
...
...
const appReducer = persistCombineReducers(
{ // cfg
key: 'primary',
storage: AsyncStorage,
blacklist: ['user'],
throttle: 1000,
stateReconciler: hardSet
},
{
user: userReducer,
status: statusReducer,
list: listReducer,
map: mapReducer,
stateReconciler: hardSet
}
);
Upvotes: 0
Reputation: 2174
Here is a simple example were you can watch AsyncStorage value being changed after state change on every button click:
import PropTypes from 'prop-types'
import React, { Component } from 'react';
import { applyMiddleware, compose, createStore } from 'redux'
import { AsyncStorage, AppRegistry, Button, StyleSheet, Text, View } from 'react-native';
import { combineReducers, Provider, connect } from 'react-redux'
import { persistStore, persistCombineReducers, autoRehydrate} from 'redux-persist'
import thunk from 'redux-thunk';
class Counter extends Component {
render() {
const { value, onIncreaseClick, checkValueInStorage} = this.props
return (
<Text>
<Text>Val: {value}</Text>
<Button title="Increase" onPress={() => {
onIncreaseClick()
checkValueInStorage()
}}/>
</Text>
)
}
}
Counter.propTypes = {
value: PropTypes.number.isRequired,
onIncreaseClick: PropTypes.func.isRequired
}
const increaseAction = { type: 'increase' }
function counter(state = { count: 0 }, action) {
const count = state.count
switch (action.type) {
case 'increase':
return { count: count + 1 }
default:
return state
}
}
const allReducers = {
counter: counter
};
const persistConfig = {
key: 'primary',
storage: AsyncStorage
}
const persistedReducer = persistCombineReducers(persistConfig, allReducers)
const rootReducer = (state, action) => {
return persistedReducer(state, action);
}
let store = createStore(rootReducer, undefined, applyMiddleware(thunk))
let pers = persistStore(store, null)
function mapStateToProps(state) {
return {
value: state.counter.count
}
}
function mapDispatchToProps(dispatch) {
return {
onIncreaseClick: () => dispatch(increaseAction),
checkValueInStorage: () => {
AsyncStorage.getItem("persist:root").then(function (specifiVal) {
console.log("persist:root: Value before delay" + JSON.stringify(specifiVal))
})
setTimeout(function () {
AsyncStorage.getItem("persist:root").then(function (specifiVal) {
console.log("Value after delay" + JSON.stringify(specifiVal))
})
}, 5000);
}
}
}
const AppCounter = connect(
mapStateToProps,
mapDispatchToProps
)(Counter)
export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<View style={styles.container}>
<Text>Counter view</Text>
<AppCounter/>
</View>
</Provider>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
}
});
Upvotes: 0
Reputation: 193
I changed:
const store = createStore(
persistReducer,
enhancers
);
to:
const store = createStore(
persistReducer,
undefined,
enhancers
);
Now its persisted if I execute store.flush() but not automatically on every state change. It's flushed on app state changes though (foreground / background).
Upvotes: 0
Reputation: 2174
Try to persist AsyncStorage
this way:
persistStore(store, {storage: AsyncStorage})
Upvotes: 0
Reputation: 2368
This is not straight answer to question, but gives alternative solution and some perspective to this issue.
I'm not familiar with redux-persist or it's functionality, but I would myself prefer doing this with making own middleware in redux.
import initState from "./reducers/InitiateReducer"
let initialState = initState
Middleware to save data to LocalStorage
const localStoreMiddleware = (store) => (next) => (action) => {
// Get previously stored state
const ls = localStorage.getItem("reduxState") || null
let state = {
application: {}
}
if (ls) {
state = { ...JSON.parse(ls) }
}
// Actions that will be required to save data to localStore
switch (action.type) {
case "APP_SESSION":
state.application.session = { ...action.payload.session }
break
case "VERIFICATION_DONE":
state.application.verification_required = false
break
}
localStorage.setItem("reduxState", JSON.stringify(state))
next(action)
}
Get LocalStorage to initialState variable and pass values
if (localStorage.getItem("reduxState") !== null) {
const ls = JSON.parse(localStorage.getItem("reduxState")) || {}
if (ls.application) {
if (ls.application.session) {
initialState.application.session = ls.application.session
}
initialState.application.verification_required =
ls.application.verification_required
}
}
Create store variable with initialState
const store = () => {
return createStore(
rootReducer,
initialState,
applyMiddleware(
thunkMiddleware,
localStoreMiddleware
)
)
}
export default store
This functionality is made using browser's localStorage but same ideology can be made use of in React Native and using asyncStorage. You then just wrap components you like to connect() and use mapStateToProps on variables you'd like to.
I'm sorry for not giving React Native ready solution, I will myself get into this during weekend more deeply.
Upvotes: 1