I have generated a project using Create React App and then added Redux.
The redux state is then split into three parts that each has its own reducer and some middleware defined. The reducers are place in files called part1.js part2.js part3.js there is then a common.js file that imports the reducer and the middleware from part1-2-3.js and adds them to combineReducer and applyMiddeware.
My question is if there is anyway to not having to import everything in one place. What I want is to be able to add the reducer and middeware to comineReducer and applyMiddleware from within part1-2-3.js, the reason is to get rid of an explicit common boilerplate code file in common.js. Is this possible or is the only way to import everything into one place?
UPDATE
I have now great examples on how to solve the combineReducer part, however I still need to do something similar for applyMiddleware. I have found an example from the following repo on how to do something similar with applyMiddleware. However its in TypeScript and I have a hard time translating it into what is the minimal way to get this working within a JS React/Redux application. Would be great with some examples.
UPDATE
So I finally found this minimal library doing what I want.
Yes! I have a reducer registry which is similar (almost identical) to this reducer manager: https://redux.js.org/recipes/code-splitting#using-a-reducer-manager:
const DEFAULT_REDUCER = state => state || null;
export class ReducerRegistry {
constructor() {
this._emitChange = null;
this._reducers = {};
}
getReducers() {
// default reducer so redux doesn't complain
// if no reducers have been registered on startup
if (!Object.keys(this._reducers).length) {
return { __: { reducer: DEFAULT_REDUCER } };
}
return { ...this._reducers };
}
register(name, reducer, options = {}) {
if (this._reducers.name && this._reducers.name !== reducer) {
throw new Error(`${name} has already been registered`);
}
this._reducers = { ...this._reducers, [name]: { reducer, options } };
if (this._emitChange) {
this._emitChange(this.getReducers());
}
}
setChangeListener(listener) {
this._emitChange = listener;
}
}
const reducerRegistry = new ReducerRegistry();
export default reducerRegistry;
Then I have my redux domains organized into folders like reducks-style: https://github.com/alexnm/re-ducks
In the index.js of the reducks domain, I import the reducer registry and register the reducer:
import Domain from './name';
import reducer from './reducer';
import { reducerRegistry } from '...wherever';
reducerRegistry.register(Domain, reducer); // register your reducer
export { ... whatever }
Finally, my store uses the reducer registry like this:
export const store = createStore(
combine(reducerRegistry.getReducers()),
initialState,
composeEnhancers(applyMiddleware(...middlewares))
);
// Replace the reducer whenever a new reducer is registered (or unregistered).!!!!
// THIS IS THE MAGIC SAUCE!
reducerRegistry.setChangeListener(reducers =>
store.replaceReducer(combine(reducers))
);
export default store;
This setup has worked magically for us. Allows us to keep all of our redux logic very isolated from the rest of the application (and from other redux domain logic!), works fantastic for code-splitting. Highly recommend it.
Yes you can. We use it with dynamic import and works well. We use with react hooks.
use store.replaceReducer https://redux.js.org/api/store#replacereducernextreducer
in configureStore (or any file when you call createStore from redux)
const store = createStore(/*...*/)
add
store.injectedReducers = {}; // Reducer registry
and create a new file with injectReducer hook.
const useInjectReducer reducer => {
const store = useStore();
const key = Object.keys(reducer)[0];
if (
Reflect.has(store.injectedReducers, key) &&
store.injectedReducers[key] === reducer[key]
) {
return;
}
store.injectedReducers = {
...store.injectedReducers,
...reducer
};
store.replaceReducer(combineReducers(store.injectedReducers));
}
and you can use it in react App:
export const TodoPage = () => {
useInjectReducer({ [REDUCER_NAME]: TodoReducer });
/*...*/
}
if you use server side rendering you need to be sure redux not cleaning up the states for missing reducers before dynamic import. You can create a dummyReducers to prevent that.
const dummyReducers = Object.keys(initialState).reduce((acc, current) => {
acc[current] = (state = null) => state
return acc;
}, {});
and add this for:
const store = createStore(
combineReducers(dummyReducers),
initialState
)
We use the same pattern to Inject Sagas.
Related
My goal is to ultimately create add a shopping cart functionality using context API. In order to do so I need to get the products from my database and store it in an array.
Currently, the challenge I'm facing is how to retrieve the data from the axios response and store it in a variable that will be passed on within a const component. Apparently, the issue is that the variable gets passed to the child before the Axios response is complete.
I tried using the await keyword, but got an error regarding not being in an async function. Hence, I tried plugging the async keyword but that didn't work as it yielded errors.
I was able to retrieve data from axios within class components with success, however, I am unable to do so in these const.
Here is my code:
Context.js
import { createContext, useContext, useReducer } from "react";
import React from "react";
import ProductService from "../services/ProductService";
import { cartReducer } from "./Reducers";
const Cart = createContext();
const Context = ({ children }) => {
let products = [];
console.log("part1", products);
ProductService.getAllProducts().then((res) => {
products = res.data; //Also tried setState({ products : res.data})
console.log("response: ", products);
});
const [state, dispatch] = useReducer(cartReducer, {
products: products,
cart: [],
});
return <Cart.Provider value={{ state, dispatch }}>{children}</Cart.Provider>;
};
export const CartState = () => {
return useContext(Cart);
};
export default Context;
ProductService.js
import axios from "axios";
const PRODUCT_BASE_URL = "http://localhost:8080/api/v1/";
class ProductService {
getAllProducts() {
return axios.get(PRODUCT_BASE_URL + "products");
}
getProductsByCategory(category) {
return axios.get(PRODUCT_BASE_URL + "products/" + category);
}
getProductById(id) {
return axios.get(PRODUCT_BASE_URL + "product/" + id);
}
}
export default new ProductService();
Reducers.js
export const cartReducer = (state, action) => {
switch (action.type) {
default:
return state;
}
};
HomeComponenet.jsx
import React from "react";
import SlideShowComponent from "./SlideShowComponent";
import HomeCategoriesComponent from "./HomeCategoriesComponent";
import FeaturedProductsComponent from "./FeaturedProductsComponent";
import { CartState } from "../context/Context";
// class HomeComponent extends React.Component
function HomeComponent() {
const { state } = CartState();
console.log("Cart Inside the Home Component: ", state);
return (
<>
<SlideShowComponent />
<div>
<HomeCategoriesComponent />
</div>
<div>
<FeaturedProductsComponent />
</div>
</>
);
}
export default HomeComponent;
First of all, I've created a fixed example here: https://stackblitz.com/edit/react-jqtcia?file=src/Context.js
You are correct, you need to await the result from axios. In React, we use the useEffect hook for things with side effects or that should not be done as part of the render. Renders in react should be non blocking, that is they should not be dependent on things like data fetching.
A simple example of this would be if we needed it in local state. This example renders without the data, then re-renders once the data is available.
const [products, setProducts] = useState([]);
useEffect(async () => {
const { data } = await ProductService.getAllProducts();
setProducts(data);
}, []);
return <div>{products.length > 0 ? `${products.length} products` : 'Loading...'</div>
NOTE: the , []); means that this will fire once, when the first render happens.
This fixes the first part of your problem, getting the result out of the request/axios.
However, the second part and most important part is that you weren't using this value. You were attempting to insert the result as part of the initial state, but this was empty by the time it was created. As you are using reducers (useReducer), this means you need to dispatch an action for each event and handle all the relevant events to data fetching in the reducer. That means, you should need to be able to handle:
Some data is in a loading state (e.g., pagination or first load)
The data failed to load
The data is partially loaded
The data has fully loaded
I've created a minimal happy example (there is no pagination and data fetching always succeeds):
Context.js
useEffect(async () => {
const { data: loadedProducts } = await ProductService.getAllProducts();
console.log('response: ', JSON.stringify(loadedProducts));
dispatch({ type: 'PRODUCTS_LOADED', products: loadedProducts });
console.log(state);
}, []);
Reducers.js
export const cartReducer = (state, action) => {
console.log(action);
switch (action.type) {
case 'PRODUCTS_LOADED':
const newState = { ...state, products: action.products };
console.log(newState);
return newState;
default:
return state;
}
};
In fact, if you'd done this with your original code, it would've worked:
ProductService.getAllProducts().then((res) => {
dispatch({ type: 'PRODUCTS_LOADED', products : res.data});
console.log("response: ", products);
});
However, this has a different bug: It will refetch the data and then dispatch the event each time Context.js is re-rendered.
Since you've asked for more information in your comment, I'll provide it here (comments were not big enough).
I've linked the relevant API documentation for the hooks above, these provide pretty good information. React does a pretty good job of explaining the what, and why of these hooks (and the library itself). Seriously, if you haven't read their documentation, you should do so.
Additional resources:
What, when and how to use useEffect - In short, if you have something to do that isn't rendering such as data fetching, it's a side-effect and should be in a useEffect.
What is a reducer in JavaScript/React/Redux - Reducers are a pattern to make shared state easier to manage and test. The basic idea is that you define a reducer, which takes an initial state and an event/action, and produces a new state. The key idea is that there must be no side-effects, the same action and state will always produce the same result, no matter the date/time, network state, etc, etc. This makes testing and reasoning about things easier, but at the cost of a more complex state management.
However, something important that I ignored in your original question is that you are kind of reinventing the wheel here. There is already a library that will centralise your state and make it available via context, it's the library that originally invented reducers: redux. I'm guessing you have read something like this article about using context instead of redux, however the advantage of redux for you is that there is a litany of documentation about how to use it and solve these problems.
My recommendation for you is to make sure you need/want redux/reducers. It has it's value and I personally love the pattern but if you are just getting started on React, you would be better off just using useState in my opinion.
I have this gtag (analytics plugin) that I can access on my components but never on my store.
I would appreciate any opinions. Thanks
plugins/vue-gtag.js
import Vue from "vue"
import VueGtag from "vue-gtag"
export default ({ app }, inject) => {
Vue.use(VueGtag, {
config: {
id: process.env.ga_stream_id
}
})
}
store/gaUserProperty.js
import Vue from "vue"
import { User } from "~/models/user/User"
export const states = () => ({})
const getterObjects = {}
const mutationObjects = {}
Object.keys(states).forEach(key => {
getterObjects[key] = state => state[key]
mutationObjects[key] = (state, value) => (state[key] = value)
})
export const state = () => states
export const getters = { ...getterObjects }
export const mutations = { ...mutationObjects }
export const actions = {
async sendUserProperties({ dispatch, commit }) {
let res = await this.$UserApi.getUser()
if (!(res instanceof User)) {
} else {
// I can access this on my components and pages but for some reason not here....
console.log(this.$gtag)
}
}
}
To import this properly, I would export the instance (or any of its internals) from main.(ts|js):
const Instance = new Vue({...whatever});
// only export what you need in other parts of the app
export const { $gtag, $store, $t, $http } = Instance;
// or export the entire Instance
export default Instance;
now you can import it in your store:
import Instance from '#/main';
// or:
import { $gtag } from '#/main';
// use Instance.$gtag or $gtag, depending on what you imported.
As other answers mentioned, in current Vuex version the Vue instance is available under this._vm inside the store. I'd refrain from relying on it, though, as it's not part of the exposed Vuex API and not documented anywhere. In other words, Vuex developers do not guarantee it will still be there in future versions.
To be even more specific, Vue's promise is that all v2 code will work in v3. But only if you use the exposed API's.
And the discussion here is not even on whether it will be removed or not (it most likely won't). To me, it's more a matter of principle: if it starts with a _ and it's not documented, it translates into a message from authors: "We're reserving the right to change this at any time, without warning!"
You can access the Vue instance through this._vm in the Vuex store, so you would just need to do:
console.log(this._vm.$gtag)
That should do the trick.
According to nuxtjs the plugins are available in the store actions.
https://nuxtjs.org/docs/directory-structure/plugins
Sometimes you want to make functions or values available across your
app. You can inject those variables into Vue instances (client side),
the context (server side) and even in the Vuex store. It is a
convention to prefix those functions with a $.
so I am trying to refactor some code from my previous question:
React: How to update one component, when something happens on another component
So I started digging deep into the existing code template to see how it was implemented.
I found a reducers.js where I added a new reducer: ActiveTenant
import Auth from './auth/reducer';
import App from './app/reducer';
import ThemeSwitcher from './themeSwitcher/reducer';
import LanguageSwitcher from './languageSwitcher/reducer';
import ActiveTenant from './activetenant/reducer';
export default {
Auth,
App,
LanguageSwitcher,
ThemeSwitcher,
ActiveTenant
};
That new reducer is like this:
import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';
const initState = new Map({
tenantId: ''
});
export default function(state = initState, action) {
switch (action.type) {
case actions.SET_TENANT_ACTIVE:
{
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
.then(response =>{
if(response.status === 200){
console.log("Tenant activated");
}else{
throw "error";
}
})
.catch(error => {
console.error(error);
});
return state.set('tenant', state.Name);
}
default:
return state;
}
}
and actions for that reducer
const actions = {
SET_TENANT_ACTIVE: 'SET_TENANT_ACTIVE',
setTenantActive: () => ({
type: actions.SET_TENANT_ACTIVE
}),
};
export default actions;
Then from the component itself, I need to call the action when a row is selected on the front end, so I have refactored the commented code, into one line.
import React, { Component } from 'react';
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';
const { setTenantActive } = actions;
class ListTenants extends Component {
constructor(props) {
super(props);
this.state = {
data: []
};
}
fetchData = () => {
adalApiFetch(fetch, "/Tenant", {})
.then(response => response.json())
.then(responseJson => {
if (!this.isCancelled) {
const results= responseJson.map(row => ({
key: row.id,
TestSiteCollectionUrl: row.TestSiteCollectionUrl,
TenantName: row.TenantName,
Email: row.Email
}))
this.setState({ data: results });
}
})
.catch(error => {
console.error(error);
});
};
componentDidMount(){
this.fetchData();
}
render() {
const columns = [
{
title: 'TenantName',
dataIndex: 'TenantName',
key: 'TenantName',
},
{
title: 'TestSiteCollectionUrl',
dataIndex: 'TestSiteCollectionUrl',
key: 'TestSiteCollectionUrl',
},
{
title: 'Email',
dataIndex: 'Email',
key: 'Email',
}
];
// rowSelection object indicates the need for row selection
const rowSelection = {
onChange: (selectedRowKeys, selectedRows) => {
if(selectedRows[0].TenantName != undefined){
console.log(selectedRows[0].TenantName);
const options = {
method: 'post'
};
setTenantActive(selectedRows[0].TenantName);
/* adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+selectedRows[0].TenantName.toString(), options)
.then(response =>{
if(response.status === 200){
Notification(
'success',
'Tenant set to active',
''
);
}else{
throw "error";
}
})
.catch(error => {
Notification(
'error',
'Tenant not activated',
error
);
console.error(error);
}); */
}
},
getCheckboxProps: record => ({
type: Radio
}),
};
return (
<Table rowSelection={rowSelection} columns={columns} dataSource={this.state.data} />
);
}
}
export default ListTenants;
However, its not clear to me the relationship between the action and the reducer, if I check the debugger the action is executed, and none parameter is received, but the reducer is never executed.
DO i have to put a dispatch somewhere?, what I am missing in this puzzle?
So the first thing to understand is the Redux Cycle:
Action Creator-->Action-->dispatch-->Reducers-->State
Action Creator: An action creator is a function that is going to create or return a plain JavaScript object knowns as an Action with a type property and payload property which describes some change you want to make on your data.
The payload property describes some context around the change we want to make.
The purpose of an Action is to describe some change to the data inside our application.
The Action Creator is to create the Action.
The dispatch function is going to take in an Action and make copies of that object and pass it off to a bunch of different places inside our application which leads us to the Reducers.
In Redux, a reducer is a function responsible for taking in an Action. Its going to process that Action, make some change to the data and return it so it can be centralized in some location.
In Redux, the State property is a central repository of all information produced by our reducers. All the information gets consolidated inside the State object so our React application can easily reach into our Redux side of the app and get access to all the data inside the application.
So this way the app does not have to go around to each separate reducer and ask for the current State.
So digest that for a couple of minutes and then look at your architecture.
Let's skip over to reducers.
Reducers are called with an Action that was created by an Action Creator. The reducer will take a look at that Action and decide whether it needs to modify some data based on that Action.
So in other words, the job of a reducer is not to execute API requests but to process actions sent to it by the action creator.
So instead of this:
import { Map } from 'immutable';
import actions from './actions';
import { adalApiFetch } from '../../adalConfig';
const initState = new Map({
tenantId: ''
});
export default function(state = initState, action) {
switch (action.type) {
case actions.SET_TENANT_ACTIVE:
{
const options = {
method: 'post'
};
adalApiFetch(fetch, "/Tenant/SetTenantActive?TenantName="+state.tenantId, options)
.then(response =>{
if(response.status === 200){
console.log("Tenant activated");
}else{
throw "error";
}
})
.catch(error => {
console.error(error);
});
return state.set('tenant', state.Name);
}
default:
return state;
}
}
Your reducer should look something like this:
import { SET_TENANT_ACTIVE } from "../actions/types";
const initialState = {
tenantId: ''
};
export default (state = initialState, action) {
switch (action.type) {
case SET_TENANT_ACTIVE:
return {...state, [action.payload.id]: action.payload };
default:
return state;
}
}
Then inside your action creators file, you should have an action creator that looks something like this:
import axios from 'axios';
import { SET_TENANT_ACTIVE } from "../actions/types";
export const setTenant = id => async (dispatch) => {
const response = await axios.post(`/tenants/${id}`);
dispatch({ type: SET_TENANT_ACTIVE, payload: response.data });
};
You also need to learn about Redux project structure because after the above refactor, you are missing how to wire all this up to your component. In your component file there is no connect() function which also requires the Provider tag and you have none of that.
So for this I recommend first of all your set up your folder and file structure like so:
/src
/actions
/components
/reducers
index.js
So inside your index.js file it should look something like this:
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import reduxThunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducers,
composeEnhancers(applyMiddleware(reduxThunk))
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
So your goal here is to ensure that you get that Provider tag at the very top of your component hierarchy and ensure that you pass it a reference to your Redux store that gets all the reducers loaded up into it.
So above I have created the store and passed it our set of reducers and it will return back to you all your applications State.
Lastly, what you see above is I created an instance of <Provider> and wrapped the <App /> component with it and then you want to pass the <Provider> component is a single prop called store. The store is the result of calling createStore() and calling the reducers.
The <Provider> is what interacts with the Redux store on our behalf.
Notice, I also have wired up Redux-Thunk that J. Hesters mentioned, you are making an ajax request as far as I can see from your code which is why I offered an asynchronous action creator for you, which means you will need Redux-Thunk or some middleware like that, let me not offend the Redux-Saga fans, so you have those two choice at least. You seem relatively new to Redux, just go with Redux-Thunk.
Now you can use the connect() component inside your component file to finish wiring up those action creators and reducers to your component or your React side of the application.
import React, { Component } from 'react';
import { connect } from "react-redux";
import { Table, Radio} from 'antd';
import { adalApiFetch } from '../../adalConfig';
import Notification from '../../components/notification';
import actions from '../../redux/activetenant/actions';
After importing connect, you create an instance of it below:
export default connect()(ListTenants);
Please don't argue with me on the above syntax (actually had a former student report me to administrators for using this syntax as evidence of not knowing what I was doing).
Then you need to configure this connect() React component by adding mapStateToProps if you are going to need it, but definitely pass in actions as the second argument to connect(). If you realize you don't need mapStateToProps, then just pass in null as the first argument, but you can't leave it empty.
Hope all this was helpful and welcome to the wonderful world of React-Redux.
You are using reducers wrong. Reducers are supposed to be pure. Yours has side-effects showing that you haven't understood Redux, yet.
Instead of writing down a solution for you (which would take forever anyways since one would have to explain Redux in total), I suggest you invest the 3 hours and go through the Redux docs and follow the tutorials (they are great).
Afterwards you might want to look into Redux Thunk. But, you might not need thunks.
PS: (Small thing to bring up, but I haven't seen anyone using Maps in Redux. Is there a reason you do that? You might want to use plain objects instead.)
Your action is not correct you should pass an active tenant name as parameter.
Ref. https://redux-starter-kit.js.org/api/createaction
We could have written the action types as inline strings in both places.
The action creators are good, but they're not required to use Redux - a component could skip supplying a mapDispatch argument to connect, and just call this.props.dispatch({type : "CREATE_POST", payload : {id : 123, title : "Hello World"}}) itself.
Ref. https://redux-starter-kit.js.org/usage/usage-guide
I have created a Network service component which deals with the API call. I want to retrieve state from other components which update the store.
Im having trouble getting the state so I started using Redux, but I havent used Redux before and still trying to find a way to pass the state to the NetworkService. Any help would be great, thanks!
Here is my NetworkService.js
import RequestService from './RequestService';
import store from '../store';
const BASE_URL = 'api.example.com/';
const REGION_ID = //Trying to find a way to get the state here
// My attempt to get the state, but this unsubscribes and
// doesnt return the value as it is async
let Updated = store.subscribe(() => {
let REGION_ID = store.getState().regionId;
})
class NetworkService {
getForecast48Regional(){
let url =`${BASE_URL}/${REGION_ID }`;
return RequestService.getRequest(url)
}
}
export default new NetworkService();
store.js
import {createStore} from 'redux';
const initialState = {
regionId: 0
};
const reducer = (state = initialState, action) => {
if(action.type === "REGIONAL_ID") {
return {
regionId: action.regionId
};
}
return state;
}
const store = createStore(reducer);
export default store;
My folder heirarchy looks like this:
-App
----Components
----NetworkService
----Store
Do not import store directly. Use thunks/sagas/whatever for these reasons.
NetworkService should not know about anything below.
Thunks know only about NetworkService and plain redux actions.
Components know only about thunks and store (not store itself, but Redux's selectors, mapStateToProps, mapDispatchToProps).
Store knows about plain redux actions only.
Knows - e.g. import's.
//////////// NetworkService.js
const networkCall = (...args) => fetch(...) // say, returns promise
//////////// thunks/core/whatever.js
import { networkCall } from 'NetworkService'
const thunk = (...args) => (dispatch, getState) => {
dispatch(startFetch(...args))
const componentData = args
// I'd suggest using selectors here to pick only required data from store's state
// instead of passing WHOLE state to network layer, since it's a leaking abstraction
const storeData = getState()
networkCall(componentData, storeData)
.then(resp => dispatch(fetchOk(resp)))
.catch(err => dispatch(fetchFail(err)))
}
//////////// Component.js
import { thunk } from 'thunks/core/whatever'
const mapDispatchToProps = {
doSomeFetch: thunk,
}
const Component = ({ doSomeFetch }) =>
<button onClick={doSomeFetch}>Do some fetch</button>
// store.subscribe via `connect` from `react-redux`
const ConnectedComponent = connect(..., mapDispatchToProps)(Component)
I am trying to keep a user logged in in my application. I tried several techniques but i have no idea on how i can read data back into the state when the application launches.
Right now i have the following:
const getInitialState = () => {
var _initState = {
auth: new AuthInitialState(),
global: (new GlobalInitialState())
};
return _initState;
};
export default function configureStore() {
const store = createStoreWithMiddleware(reducer, load(APP_STORAGE) || getInitialState());
store.subscribe(() => {
if(!load('debug')) {
save(APP_STORAGE, store.getState());
}
});
return store;
};
const createStoreWithMiddleware = applyMiddleware(
thunk,
localStorageMiddleware,
logger
)(createStore)
In which the load and save methods are responsible for saving data to an AsyncStorage (using react-native-simple-store)
export const load = (key) => {
return store.get(key);
}
export const save = async (key, data) => {
store.save(key, JSON.stringify(data));
}
The render of my root is the current:
render() {
const store = configureStore();
return (
<Provider store={store}>
<MyApp/>
</Provider>
);
}
The data is being correctly saved (through the save subscriber) but it is not correctly reloaded on a hot reload or app relaunch. Thus my user ends up being logged out every time.
In the end i would also like to apply this technique to navigate to the correct page upon app startup.
Any recommendations on how i can approach this?
You can use redux-persist to achieve this:
import { createStore, applyMiddleware, compose } from 'redux';
import { persistStore, autoRehydrate } from 'redux-persist';
import { AsyncStorage } from 'react-native';
export default function configureStore() {
const store = createStore(reducers, getInitialState(), compose(
applyMiddleware([
thunk,
localStorageMiddleware,
logger
]),
autoRehydrate()
)
);
persistStore(store, { storage: AsyncStorage });
return store;
};
With this, each time your application load, the store is hydrated from the local storage. You don't have to deal with the AsyncStorage, everything is done automatically for you. You can read the docs of redux-persist to customize it per your needs (add a whitelist, a blacklist, a callback when the store is rehydrated..)
Your basic approach looks good to me.
However, react-native-simple-store stringifies the state for you. As you run JSON.stringify() in your save function as well, it will not properly get decoded when it is loaded during the next start of your app.
See react-native-simple-store's codebase for more details.
To resolve this, remove JSON.stringify() from your save function:
export const save = async (key, data) => {
store.save(key, data);
}