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;
Related
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
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>
);
};
So, I have searched high and low for a solution to this problem and haven't found an answer. Any closely related questions end up solving the problem by fixing state mutation issues which I am pert near positive I don't have. Also, I have done similar things a few times before that have worked just fine, and referring back to the code for those projects shown no significant differences with what I'm doing here.
I have a small React app that will get bigger over time so I'm using Redux for state management. Currently I have a main App component that will eventually be the parent for my routing, but for now I just have a single component inside of it that will be a Login page. The current thing I am trying to accomplish is simply to change the value of showCreateAccountFlyout from false to true when I click ButtonPseudoAnchor in the Login page.
App.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
// import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
/* ---- Routes ----- */
import Login from './scenes/Login';
/* ----- Redux Store ----- */
import store from './redux/configureStore';
/* ----- Styles ----- */
import reset from './globalStyles/reset.scss';
export default class App extends React.Component<void> {
render() {
return (
<Provider store={store}>
<Login />
</Provider>
)
}
}
Login.js
import React from 'react';
import { Link } from 'react-router-dom';
import ButtonBasic from '../components/buttons/ButtonBasic';
import ButtonRow from '../components/buttons/ButtonRow';
import ButtonPseudoAnchor from '../components/buttons/ButtonPseudoAnchor';
import FlyoutFullScreen from '../components/containers/FlyoutFullScreen';
import FormElementBasic from '../components/formElements/FormElementBasic';
import PageBasic from '../components/containers/PageBasic';
import { connect } from 'react-redux';
import { toggleCreateAccountFlyout } from '../redux/actions/loginActions';
/* ----- Styles ----- */
import loginStyles from './loginStyles.scss';
class Login extends React.Component {
constructor() {
super();
this.onClickHandler = this.onClickHandler.bind(this);
}
onClickHandler() {
this.props.toggleCreateAccountFlyoutHandler(!this.props.showCreateAccountFlyout);
}
render() {
return (
<PageBasic contextualModifier="loginPage">
<div className={loginStyles.loginContainer}>
<div className={loginStyles.baphometLogo}>baphomet</div>
<FormElementBasic
type="text"
placeholderText="Username or Email"
/>
<FormElementBasic
type="password"
placeholderText="Password"
/>
<ButtonRow rowAlignment="center">
<ButtonBasic
buttonText="Enter"
buttonType="primary"
/>
</ButtonRow>
<ButtonPseudoAnchor onClickFunc={this.onClickHandler}>Create Account</ButtonPseudoAnchor>
</div>
<FlyoutFullScreen showFlyout={this.props.showCreateAccountFlyout} />
</PageBasic>
);
}
}
const mapStateToProps = state => {
return {
showCreateAccountFlyout: state.loginReducer.showCreateAccountFlyout
}
};
const mapDispatchToProps = dispatch => {
return {
toggleCreateAccountFlyoutHandler: showCreateAccountFlyout =>
dispatch(toggleCreateAccountFlyout(showCreateAccountFlyout))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
loginActions.js
export const TOGGLE_CREATE_ACCOUNT_FLYOUT = 'TOGGLE_CREATE_ACCOUNT_FLYOUT';
export function toggleCreateAccountFlyout(showCreateAccountFlyout) {
return {
type: TOGGLE_CREATE_ACCOUNT_FLYOUT,
payload: showCreateAccountFlyout
}
}
loginReducer.js
import { TOGGLE_CREATE_ACCOUNT_FLYOUT } from '../actions/loginActions';
export default function loginReducer(state = {
showCreateAccountFlyout: false
}, action) {
switch(action.type) {
case TOGGLE_CREATE_ACCOUNT_FLYOUT:
return { ...state, showCreateAccountFlyout: action.payload };
default:
return state;
}
}
reducers/index.js
import { combineReducers } from 'redux';
import loginReducer from './loginReducer';
const rootReducer = combineReducers({
loginReducer
});
export default rootReducer;
configureStore.js
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
const middleware: Function = applyMiddleware(thunk);
export default createStore(rootReducer, middleware);
As I step through everything, the new showCreateAccountFlyout value is being passed along, it just appears to never get passed back to the Login component from the reducer. Maybe there is something super simple I'm missing? I dunno.
I want to blacklist some of my reducers because my state tree is getting bigger and im getting this error:
Could not write debug session to localStorage: DOMException: Failed to execute 'setItem' on 'Storage': Setting the value of 'redux-persist' exceeded the quota.(…)"
The solution I found is to blacklist some reducers that doesn't need to be persisted. So I have this code in my App.js
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { persistStore } from 'redux-persist'
import { Initializer } from './components';
import store from './store';
class App extends Component {
constructor() {
super()
this.state = { rehydrated: false }
}
componentWillMount(){
persistStore(store, { blacklist: ['project', 'comment', 'project', 'jobOrder']}, () => {
this.setState({ rehydrated: true })
})
}
render() {
if(!this.state.rehydrated)
return <Initializer />;
return (
<Provider store={store}>
<Router>
<div>
<Switch>
... some Routes
</Switch>
</div>
</Router>
</Provider>
);
}
}
export default App;
and I have this rootReducer:
import { reducer as formReducer } from 'redux-form'
import { combineReducers } from 'redux';
import userAuthReducer from './userAuthReducer';
import jobOrderReducer from './jobOrderReducer';
import clientReducer from './clientReducer';
import userReducer from './userReducer';
import persistReducer from './persistReducer';
import commentReducer from './commentReducer';
import projectReducer from './projectReducer';
import teamReducer from './teamReducer';
export default combineReducers({
userAuth: userAuthReducer,
jobOrder: jobOrderReducer,
job_order: jobOrderReducer,
client: clientReducer,
user: userReducer,
form: formReducer,
persist: persistReducer,
comment: commentReducer,
project: projectReducer,
team: teamReducer
});
My persistReducer.js
import { PERSIST } from '../actions/types';
export default (state = [], action) => {
switch(action.type) {
case PERSIST:
return { ...state, ...action.payload }
default:
return state;
}
};
And my store.js
import { compose, createStore, applyMiddleware } from 'redux';
import { autoRehydrate } from 'redux-persist';
import thunk from 'redux-thunk';
//import logger from 'redux-logger';
import rootReducer from './reducers';
const store = createStore(
rootReducer,
{},
compose(
applyMiddleware(thunk, /*logger*/),
autoRehydrate())
);
//persistStore(store);
export default store;
But running the App, I still get the blacklisted persisted state as you can see here:
I tried changing the blacklist keys to:
persistStore(store, { blacklist: ['reduxPersist:project', 'reduxPersist:comment', 'reduxPersist:project', 'reduxPersist:jobOrder']}, () => {
this.setState({ rehydrated: true })
})
But the keys are still persisting... How to properly do this?
I read this article here and the author made a good point, the blacklist isn't looking for the name of the attributes in your redux store, rather in your state. I had the same confusion.
Double check in dev tools what is actually being saved in your local storage. make sure you are blacklisting the correct names.
You need to define blacklist array in both config objects (parent and child).
const rootPersistConfig = {
key: 'root',
storage: AsyncStorage,
blacklist: ['appReducer'], // 2: Need to add child reducer key here too.
};
const appPersistConfig = {
key: 'appReducer',
storage: AsyncStorage,
// 1: Below are the keys to blacklist
blacklist: ['snackbar', 'dayThought', 'updatingBohoId', 'snackbar_notif'],
};
I'm working toward having my React/Redux app update the URL based on actions. I've done quite a bit of looking around. I thought I had a handle on it, but obviously I'm missing something. I have other reducers that respond correctly.
Currently, I'm just trying to log the action.
Routing Reducer
const initialState = { locationBeforeTransitions: null };
export default function routing(state = initialState, action)
{
switch (action.type)
{
case 'LOCATION_CHANGE':
console.log(action)
default:
return state
}
}
Store
/*
Things from other people
*/
import { createStore, applyMiddleware, compose } from 'redux'
import { syncHistoryWithStore } from 'react-router-redux';
import { browserHistory } from 'react-router'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
/*
Things from us
*/
import { fetchSuitesAndFails, fetchResults } from './actions/actions';
import rootReducer from './reducers/index'
const enhancers = compose(
window.devToolsExtension ? window.devToolsExtension() : f => f
);
const loggerMiddleware = createLogger()
/*
Store
*/
export const store = createStore(
rootReducer,
enhancers,
applyMiddleware(
thunkMiddleware, // lets us dispatch() functions
loggerMiddleware // neat middleware that logs actions
)
);
// Export the history so we can keep track of things
export const history = syncHistoryWithStore(browserHistory, store);
/*
Enable Hot Reloading for the reducers
We re-require() the reducers whenever any new code has been written.
Webpack will handle the rest
*/
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('./reducers/index', () => {
const nextRootReducer = require('./reducers/index').default
store.replaceReducer(nextRootReducer)
})
}
Index
/*
React router needs
*/
import { combineReducers } from 'redux'
import { routerReducer } from 'react-router-redux'
/*
Reducers
*/
import suite from './suite'
import suitesandfails from './suitesandfails'
import routing from './routing'
/*
Put them all together and return a single reducer
*/
const rootReducer = combineReducers({
suite,
suitesandfails,
routing,
routing: routerReducer
})
export default rootReducer
you can use string "##router/LOCATION_CHANGE" to catch the action.
react-router-redux provides const for that
import { LOCATION_CHANGE } from 'react-router-redux'
....
case LOCATION_CHANGE:
console.warn('LOCATION_CHANGE from your reducer',action)
return state
webpackbin DEMO
routerReducer code from react-router-redux
You can also import the constant from the 'react-router-redux'.
import { LOCATION_CHANGE } from 'react-router-redux'
...
case LOCATION_CHANGE:
console.warn('LOCATION_CHANGE from your reducer',action)
return state