How to write localStorage in a Redux app using middleware? - javascript

I am creating an app which fetches the list of cities entered by a user and their respective weather using openweather API. I want the cities, as a result, to remain after I refresh the page. I know how to persist the data using browser localStorage API. But I don't know how to persist it through a middleware and pass it to the provider.
Below is my main Index.js file-:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import ReduxPromise from 'redux-promise';
import App from './components/app';
import reducers from './reducers';
import {loadState,saveState} from './localStorage';
const persistedState=loadState();
const store=createStore(persistedState);
store.subscribe(()=>{
store.getState()
});
const createStoreWithMiddleware =
applyMiddleware(ReduxPromise)(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<App />
</Provider>
, document.querySelector('.container-fluid'));
This is the localStorage.js file-:
export const loadState=()=>{
try{
const serializedState=localStorage.getItem('state');
if(serializedState===null){
return undefined;
}
return JSON.parse(serializedState);
}catch(err){
return undefined;
}
};
export const saveState=(state)=>{
try {
const serializedState=JSON.stringify(state);
localStorage.setItem('state',serializedState);
}catch(err){
console.log(err);
}};
I have written the reducer in different files in reducers folder.
reducers/index.js -:
import { combineReducers } from 'redux';
import WeatherReducer from './reducer_weather';
const rootReducer = combineReducers({
weather:WeatherReducer
});
export default rootReducer;
and another reducer in
reducers/reducer_weather.js -:
import {FETCH_WEATHER} from '../actions/index';
import {DELETECITY} from '../actions/deleterow';
export default function(state=[],action){
switch(action.type){
case FETCH_WEATHER:
console.log([action.payload.data, ...state]);
return [action.payload.data, ...state];
case DELETECITY:
console.log([...state].filter((weather)=>{
return weather.city.id!==action.payload
}));
return [...state].filter((weather)=>{
return weather.city.id!==action.payload
});
default:
return state;
};
}
Before using the localStorage for persisting the application state, my code was working fine. But now it is showing me an error. I am facing a problem regarding const createStoreWithMiddleware =
applyMiddleware(ReduxPromise)(createStore);
how to use store using this middleware and how to pass in <Provider> tag's store property?

I would suggest you to use redux-persist.
Persist and rehydrate a redux store
Using this middleware all the data you have in the Redux Store will be automatically persisted to the localStorage (different type of storage can be used too). Therefore, when you refresh a page, the already persisted data from the previous request will be automatically rehydrated (pushed) back to the Store.
Example, taken from documentation:
Configuration:
// configureStore.js
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web and AsyncStorage for react-native
import rootReducer from './reducers'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default () => {
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
}
Usage:
import { PersistGate } from 'redux-persist/integration/react'
// ... normal setup, create store and persistor, import components etc.
const App = () => {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RootComponent />
</PersistGate>
</Provider>
);
};

Related

Nextjs Redux getState undefined

Hi i have simple project in Nextjs .I want to get the state of redux store but when i am trying to use store.getState it's throwing one error getState of undefined. And I have one simple vinalla javascript file i want to use getState inside that file also .How can i do that.
//store.js
import { useMemo } from "react";
import { createStore, applyMiddleware } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import { rootReducer } from "./reducer";
import { exampleInitialState } from "./reducer";
let store;
const persistConfig = {
key: "primary",
storage,
whitelist: ["exampleData", "count"], // place to select which state you want to persist
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
function makeStore(initialState = exampleInitialState) {
return createStore(
persistedReducer,
initialState,
composeWithDevTools(applyMiddleware())
);
}
export const initializeStore = (preloadedState) => {
let _store = store ?? makeStore(preloadedState);
// After navigating to a page with an initial Redux state, merge that state
// with the current state in the store, and create a new store
if (preloadedState && store) {
_store = makeStore({
...store.getState(),
...preloadedState,
});
// Reset the current store
store = undefined;
}
// For SSG and SSR always create a new store
if (typeof window === "undefined") return _store;
// Create the store once in the client
if (!store) store = _store;
return _store;
};
export function useStore(initialState) {
const store = useMemo(() => initializeStore(initialState), [initialState]);
return store;
}
//_app.js
import { useStore } from '../store'
import { Provider } from 'react-redux'
import { persistStore } from 'redux-persist'
import { PersistGate } from 'redux-persist/integration/react'
export default function App({ Component, pageProps }) {
const store = useStore(pageProps.initialReduxState)
const persistor = persistStore(store, {}, function () {
persistor.persist()
})
return (
<Provider store={store}>
<PersistGate loading={<div>loading</div>} persistor={persistor}>
<Component {...pageProps} />
</PersistGate>
</Provider>
)
}
vinall.js - How can i use store.getState in this file
import React from "react";
import { useStore } from "../store";
function vanilla() {
return <div>i m vanilla js file</div>;
}
export default vanilla;
//Also in this file
import { useStore } from "../store";
function simplejs() {
let state=useStore.getState();
return state.tods;
}
To access your redux store you can import the useSelector hook from react-redux and use it like so:
const yourStateProp = useSelector((state) => state.yourStateProp);
const myPersistReducer = persistReducer(persistConfig **as any**, rootReducer)
const store = createStore(myPersistReducer, applyMiddleware(sagaMiddleware));`
strong text
`It can be asserted that its type is declared for use

How to access react-redux store outside react component in redux-toolkit?

I am trying to access redux-store in my helper function.
My store looks like code below:
import { combineReducers, configureStore } from '#reduxjs/toolkit';
import createSagaMiddleware from 'redux-saga';
import counterReducer from 'app/Components/Counter/counterSlice';
import languageReducer from 'redux/features/app_language/language.slice';
import loginReducer, {
currentUserReducer,
currentUserIdReducer,
} from 'redux/features/app_login/loginSlice';
import rootSaga from 'redux/sagas';
import emailEditorReducer from 'redux/features/email_editor';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const configureCustomStore = () => {
const sagaMiddleware = createSagaMiddleware();
const persistConfig = {
key: 'root',
storage,
};
const rootReducer = combineReducers({
counter: counterReducer,
app_language: languageReducer,
isAuthenticated: loginReducer,
currentUser: currentUserReducer,
currentUserId: currentUserIdReducer,
emailEditor: emailEditorReducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = configureStore({
reducer: persistedReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}).concat(sagaMiddleware),
});
const persistor = persistStore(store);
sagaMiddleware.run(rootSaga);
return { store, persistor };
};
export default configureCustomStore();
export const { store, persistor } = configureCustomStore();
console.log(store.getState(), 'State');
As you can see in the last two lines of code i am trying to console.log the state and i am getting the state as expected. But when i am importing the store in my helper function i am getting undefined. Here is how i am doing it
import storeSaga from 'redux/store/store';
const { store } = storeSaga;
Error i am getting is:
TypeError: Cannot destructure property 'store' of 'redux_store_store__WEBPACK_IMPORTED_MODULE_1__.default' as it is undefined.
I was searching for the solution i end up to this who got almost same issue, the answer is also mentioned in the next comment but honestly i didn't understand it.
I may have done something wrong in order of imports i tried all the ways i could.
For the reference you can have a look on my index.js file below:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { Provider } from 'react-redux';
import { I18nextProvider } from 'react-i18next';
import * as serviceWorker from './serviceWorker';
import i18n from './_helpers/utils/i18n';
import storeSaga from 'redux/store/store';
const { store, persistor } = storeSaga;
import App from './app/Pages/App/App';
import { PersistGate } from 'redux-persist/integration/react';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<PersistGate persistor={persistor}>
<I18nextProvider i18n={i18n}>
<App />
</I18nextProvider>
</PersistGate>
</Provider>
</React.StrictMode>,
document.getElementById('root'),
);
serviceWorker.unregister();
I appreciate your help so much. Thanks in advance.
In
export default configureCustomStore();
export const { store, persistor } = configureCustomStore();
you are creating two independent stores.
Also, when importing you import the first of them and call it storeSaga even though this has nothing to do with a saga at all.
Kick the default export out and do import { store } from 'redux/store/store'; everywhere.
That makes it much clearer what you are doing.
If you still have errors after that, you have a circular import somewhere that you need to resolve, meaning that this file imports from a file that also imports this file (maybe with a third file in-between), forming a circle. JavaScript cannot work with that.
You can Inject Store to any file outside react component like this example:
File axios:
let store
export const injectStore = _store => {
store = _store
}
axiosInstance.interceptors.request.use(config => {
config.headers.authorization = store.getState().auth.token
return config
})
File index (root of your project)
import store from './app/store'
import {injectStore} from './common/axios'
injectStore(store)
You can also read about this here:
https://redux.js.org/faq/code-structure#how-can-i-use-the-redux-store-in-non-component-files

How to persist redux state with gatsby?

I'm using redux with gatsby by installing these packages :
react react-redux gatsby-plugin-react-redux
This is store.js file :
import { createStore } from 'redux';
import { reducer } from './reducer';
export default (preloadedState) => {
return createStore(reducer, preloadedState);
};
gatsby-config.js
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-react-redux`,
options: {
pathToCreateStoreModule: './src/redux/store',
cleanupOnClient: false
}
}
]
};
I've set cleanupOnClient to false but still when I refresh the page , the state is still the old version and it has not been persisted.
How can I persist redux state changes with gatsby ?
I added redux-persist to gatsby-plugin-react-redux, you can find it here:
https://www.npmjs.com/package/gatsby-plugin-react-redux-persist
it's the first release but seems to work :)
edit:
I see that redux-persist goes to timeout during rehydratation when using preloadedState, I don't know if it happens to you too, btw you can skip it:
export default () => {
const store = createStore(
persistedReducer,
{}, // initial state
);
const persistor = persistStore(store);
return { store, persistor };
}
I like the idea behind the persist plugin, but I found that with the createStore composition, it was easy to cause issues for other libraries wanting access to the store.
Instead I added redux-persist to my existing gatsby-plugin-react-redux setup:
createStore.js
import { compose, createStore } from 'redux'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import initialState from './initialState'
import rootReducer from './rootReducer'
const persistedReducer = persistReducer({
key: 'root',
storage
}, rootReducer);
const composeEnhancers = (typeof window === 'object') ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : undefined
const store = () => {
return createStore(
persistedReducer,
initialState,
composeEnhancers ? composeEnhancers() : compose
);
}
gatsby-browser.js wrapper
import React from 'react'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'
import { persistStore } from 'redux-persist'
import createStore from './src/state/createStore'
export const wrapRootElement = ({ element, props }) => {
const store = createStore()
const persistor = persistStore(store)
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
{element}
</PersistGate>
</Provider>
)
}

Reducers are gone after attempting to implement redux-persist

I am attempting to implement redux-persist to persist a specific reducer only, invoices, version, but in my case, my reducers are all gone.
This is the entry point:
import React, { Component } from "react";
import { Provider } from "react-redux";
import { createStore, applyMiddleware, compose } from "redux";
import { persistStore, persistCombineReducers } from "redux-persist";
import { createWhitelistFilter } from "redux-persist-transform-filter";
import { PersistGate } from 'redux-persist/es/integration/react';
import storage from "redux-persist/lib/storage";
import thunkMiddleware from "redux-thunk";
import { apiMiddleware } from "redux-api-middleware";
import reducers from "./actions/reducers";
import AppContainer from "./containers/App";
import FlashMessage from "./containers/common/FlashMessage";
import "./App.css"
const middleware = [thunkMiddleware, apiMiddleware];
const persistConfig = {
storage,
key: 'root',
transforms: [
createWhitelistFilter('invoices')
]
};
const store = createStore(
persistCombineReducers(persistConfig, reducers),
compose(
applyMiddleware(...middleware),
)
);
const persistor = persistStore(store);
export default class App extends Component {
render () {
return (
<Provider store={store}>
<PersistGate persistor={persistor}>
{/*<FlashMessage/>*/} // Since there is no reducer, this fails
<AppContainer/>
</PersistGate>
</Provider>
)
}
}
And this is the reducers file I have:
import { combineReducers } from "redux";
import { routerReducer as router } from "react-router-redux";
import app from "./app";
import invoices from "./invoices/invoices";
import recipients from "./recipients/recipients";
export const makeRootReducer = (asyncReducers) => {
return combineReducers({
router,
app,
invoices,
recipients,
...asyncReducers
})
};
export const injectReducer = (store, { key, reducer }) => {
store.asyncReducers[key] = reducer;
store.replaceReducer(makeRootReducer(store.asyncReducers))
};
export default makeRootReducer;
I have followed many suggestions but I must be missing something.
I found out why. After checking the source code, I saw the following documentation block of persistCombineReducers:
/**
* It provides a way of combining the reducers, replacing redux's #see combineReducers
* #param config persistence configuration
* #param reducers set of keyed functions mapping to the application state
* #returns reducer
*/
export function persistCombineReducers<S>(config: PersistConfig, reducers: ReducersMapObject): Reducer<S & PersistedState>;
The key words were "replacing redux's #see combineReducers".
Removing combineReducers from my reducers file to the following solved the issue:
import { routerReducer as router } from "react-router-redux";
import app from "./app";
import invoices from "./invoices/invoices";
import recipients from "./recipients/recipients";
const reducers = {
router,
app,
invoices,
recipients,
};
export default reducers;

Cannot read state from Redux store. What did I miss?

Here is my index.js where I initially dispatch an action to read my list of locations:
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import configureStore from './store/configureStore';
import {Provider} from 'react-redux';
import { Router, browserHistory } from 'react-router';
import routes from './routes';
import {loadLocationList} from './actions/locationActions';
import './css/styles.css';
const store = configureStore();
render(
<Provider store={store}>
<Router history={browserHistory} routes={routes} />
</Provider>,
document.getElementById('app')
);
Then here is my action where I get the data & then create an action out of it:
export function loadLocationListSuccess(alistingData) {
return { type: types.LOAD_LOCATION_LIST_SUCCESS, listingData: alistingData};
}
export function loadLocationList() {
return function(dispatch){ //we return a function that accepts a parameter, we just called it dispatch
//dispatch(fetchCallActions.fetchCallStart("")); // we dispatch a function fetchCallStart to indicate the start of our call, this is to keep in check with our asynchronous function calls
let link = 'http://example.com:8399/location';//our fetch url
console.log(link); //we log our link, just for debug purposes
return fetch(link) //start fetch
.then(function(response) {
return response.json();
}).then(function(json) {
dispatch(loadLocationListSuccess(json));
}).catch(function(ex) {
console.log('parsing failed', ex);
});
};
}
Then here is my reducer:
import * as types from '../actions/actionTypes';
export default function locationReducer(state = [], action) {
switch(action.type) {
case types.LOAD_LOCATION_LIST_SUCCESS:
return {listingData: action.listingData};
default:
return state;
}
}
Then here is my mapStateToProps & connect function:
function mapStateToProps(state, ownProps) {
return {
// we'll call this in our component -> this.props.listingData
listingData: state.listingData
};
}
export default connect(mapStateToProps, mapDispatchToProps)(homePage);
For some reason, it cannot read state.listingData or am I actually doing it wrongly? Anyone can help me with this problem?
I tried logging state.listingData and it showed undefined
Here is my configureStore:
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducers';
import reduxImmutableStateInvariant from 'redux-immutable-state-invariant';
import thunk from 'redux-thunk';
export default function configureStore(initialState) {
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk, reduxImmutableStateInvariant())
);
}
Here is my combined Reducer:
import {combineReducers} from 'redux';
import courses from './courseReducer';
import locations from './locationReducer';
const rootReducer = combineReducers({
courses,
locations
});
export default rootReducer;
Did I not connect it to the store properly?
Recent update:
Logging JSON.stringify(state) in mapStateToProps would actually shows the result. Thanks guys.
The correct path turned out to be state.locations.listingData because I think in my combined Reducer I included the reducer as locations so maybe thats why the state for it is state.locations. Hope this helps anyone with the problem.
Can you show the code of configureStore file? The problem might be there, may be you forgot to add reducer to list of reducers.
Does the action works right? Did you log data before dispatch(loadLocationListSuccess(json));?
UPD:
Because of rootReducer. Each reducer creates their own key in store. When you combine your reducers in rootReducer, like:
import locations from './locationReducer';
const rootReducer = combineReducers({
courses,
locations
});
It creates store with this kind of structure:
const store = {
courses: {},
locations: {}
}
So, after that you dispatched action and reducer changed the data to this:
const store = {
courses: {},
locations: {
listingData: someData
}
}
If you want to access to listingData like: state.listingData, you need to change a little your reducer and combineReducer to:
export default function listingData(state = {}, action) {
switch(action.type) {
case types.LOAD_LOCATION_LIST_SUCCESS:
return action.listingData;
default:
return state;
}
}
...
import listingData from './locationReducer';
const rootReducer = combineReducers({
courses,
listingData
});

Categories

Resources