2
votes

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]

5

5 Answers

1
votes

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.

0
votes

Try to persist AsyncStorage this way:

persistStore(store, {storage: AsyncStorage})
0
votes

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

0
votes

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'
    }
});
0
votes

Easy fix

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