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'],
};
Related
I tried to implement Redux-Persist and this is the code I wrote:
import { createStore, combineReducers } from 'redux';
import { usersReducer } from './users';
import { eventsReducer } from './events';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const persistConfig = {
key: 'root',
storage
}
const reducer = combineReducers({ users: usersReducer, events: eventsReducer })
const persistedReducer = persistReducer(persistConfig, reducer);
const store = createStore(persistedReducer);
const persistor = persistStore(store);
export { store, persistor };
My store.js file:
import { createStore, combineReducers } from 'redux';
import { usersReducer } from './users';
import { eventsReducer } from './events';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
const persistConfig = {
key: 'root',
storage
}
const reducer = combineReducers({ users: usersReducer, events: eventsReducer })
const persistedReducer = persistReducer(persistConfig, reducer);
const store = createStore(persistedReducer);
const persistor = persistStore(store);
export { store, persistor };
My usersReducer.js file (eventsReducer.js is built like this one):
const initialState = {
loggedIn: false,
thisUser: []
}
export function usersReducer(state = initialState, action) {
switch (action.type) {
case 'users/loggedIn':
return { ...state, loggedIn: action.payload }
case 'users/addUser':
return { ...state, thisUser: action.payload[0] }
case 'users/setActivated':
return { ...state, thisUser: { ...state.thisUser, activated: action.payload } }
case 'clearAll':
return {
thisUser: []
}
default:
return state
}
}
My index.js file:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux'
import { store, persistor } from './store/store';
import { PersistGate } from 'redux-persist/integration/react'
ReactDOM.render(
<Provider store={store}>
<PersistGate persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root')
);
My problem is: Redux-Persist works and is saved in localStorage, but when I refresh the page, my data saved in localStorage with Redux-Persist don't update my local state and will be empty. I think that on any refresh of the page I should get persisted data from localStorage and send back into my Redux state.
I don't know if is correct what I say but I need help how to resolve this problem.
I'm trying to use Redux, Redux-Thunk in my app. I did it before and it worked but here it keeps showing Error: Expected the reducer to be a function
scr/App.js;
import React, { Component } from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
import { Chart } from 'react-chartjs-2';
import { ThemeProvider } from '#material-ui/styles';
import { chartjs } from './helpers';
import theme from './theme';
import 'react-perfect-scrollbar/dist/css/styles.css';
import './assets/scss/index.scss';
import Routes from './Routes';
import { Provider } from 'react-redux';
// redux
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension/developmentOnly';
import * as actionTypes from './store/actions/actionTypes';
import rootReducer from './store/reducers/rootReducer';
const browserHistory = createBrowserHistory();
Chart.helpers.extend(Chart.elements.Rectangle.prototype, {
draw: chartjs.draw
});
const store = createStore(
rootReducer,
compose(applyMiddleware(thunk), composeWithDevTools)
);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<ThemeProvider theme={theme}>
<Router history={browserHistory}>
<Routes />
</Router>
</ThemeProvider>
</Provider>
);
}
}
when I tried to log rootReducer it logged an empty line for the initial load then the app breaks then it logs the rootReducer
src/store/rootReducer.js
import { combineReducers } from 'redux';
import searchReducer from './searchReducer';
const rootReducer = combineReducers({
searchReducer: searchReducer
});
export default rootReducer;
src/store/reducers/searchReducer.js
import * as actionTypes from '../actions/actionTypes';
const initialState = {
loading: false,
result: [],
error: ''
};
const searchReducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.SEARCH_TRIGGER:
return {
...state,
loading: true,
result: [],
error: ''
};
case actionTypes.SEARCH_ERROR:
return {
...state,
loading: false,
result: [],
error: action.error
};
case actionTypes.SEARCH_SUCCESS:
return {
...state,
loading: false,
error: '',
result: action.result
};
default:
return state;
}
};
export default searchReducer;
src/store/actions/actionTypes.js
// create campaign actions
export const UPDATE_STEPPER_STATE = 'UPDATE_STEPPER_STATE';
export const SEARCH_TRIGGER = 'SEARCH_TRIGGER';
export const SEARCH_SUCCESS = 'SEARCH_SUCCESS';
export const SEARCH_ERROR = 'SEARCH_ERROR';
src/store/actions/stepperActions.js
import * as actionTypes from './actionTypes';
export const updateStepperState = data => {
return {
action: actionTypes.UPDATE_STEPPER_STATE,
data
};
};
exported in index.js inside the same directory
export { updateStepperState } from './stepperActions';
packages
"react-redux": "^7.1.3",
"redux": "^4.0.4",
"redux-devtools-extension": "^2.13.8",
"redux-thunk": "^2.3.0",
"thunk": "0.0.1",
It looks like your path to searchReducer is incorrect.
Change this:
import searchReducer from './searchReducer';
to this:
import searchReducer from './reducers/searchReducer';
in your rootReducer.js file.
It's giving you the error Expected the reducer to be a function because searchReducer is returning undefined.
hey try creating store like this
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
i think you can not use multiple store enhancers together,you either choose compose or composeWIthDevTools,and create store based on environment like for development use composeWithDevTools and for production use compose
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;
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
});
My code is working fine, but I have an annoying problem whenever I make a coding mistake and get a runtime error. For instance, in one of my JSX pages I did Date() instead of new Date() and instead of reporting the actual error, I got...
Uncaught Error: Expected the reducer to be a function.
Any error I make almost always shows up as this. It's being reported from createStore.js, which is in my configureStore.jsx code below.
Is there a way that I can get better error reporting that helps me identify the real problem? Any help or ideas are greatly appreciated!!!
Here's my setup for reference....
main.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ReduxRouter } from 'redux-router';
import configureStore from './store/configureStore'
import routes from './routes';
const rootEl = document.getElementById('app-container');
const store = configureStore();
ReactDOM.render(
<div>
<Provider store={store}>
<ReduxRouter routes={routes} />
</Provider>
</div>
, rootEl
);
configureStore.jsx
import { createHashHistory } from 'history';
import { applyMiddleware, createStore, compose } from 'redux';
import { reduxReactRouter } from 'redux-router';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import rootReducer from '../reducers/rootReducer';
import routes from '../routes';
export default function configureStore(initialState = {}) {
const history = createHashHistory();
const middlewares = [
thunk,
promiseMiddleware({
promiseTypeSuffixes: ['PENDING','SUCCESS','ERROR']
})
];
const toolChain = [
applyMiddleware(...middlewares),
reduxReactRouter({
routes,
history
})
];
const store = compose(...toolChain)(createStore)(rootReducer, initialState);
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer = require('../reducers/rootReducer');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
rootReducer.jsx
import { combineReducers } from 'redux';
import { routerStateReducer } from 'redux-router';
import siteReducer from './siteReducer';
const rootReducer = combineReducers({
router: routerStateReducer,
sites: siteReducer
});
export default rootReducer;
siteReducer.jsx
import {GET_SITES} from '../actions/siteActions';
const defaultState = {
isPending: null,
isSuccess: null,
isError: null,
error: null,
data: null
};
export default function siteReducer(state = defaultState, action) {
switch (action.type) {
case `${GET_SITES}_PENDING`:
return {
...defaultState,
isPending: true
};
case `${GET_SITES}_SUCCESS`:
return {
...defaultState,
isSuccess: true,
error: false,
data: action.payload
};
case `${GET_SITES}_ERROR`:
return {
...defaultState,
isError: true,
error: action.payload
};
default:
return state;
}
}
Change the following line:
const nextRootReducer = require('../reducers/rootReducer');
To:
const nextRootReducer = require('../reducers/rootReducer').default;
Use export const variable_name instead of const variable_name whenever you want to export that variable.
For ex: rootReducer.jsx should be re-written as
import { combineReducers } from 'redux';
import { routerStateReducer } from 'redux-router';
import siteReducer from './siteReducer';
export const rootReducer = combineReducers({
router: routerStateReducer,
sites: siteReducer
});
export default rootReducer;
Note the extra export specifier with const rootReducer
My issue was importing Store from the root reducer path rather than the actual bundled store root (with devtools on the window and root reducer, middleware being composed, etc).
import Store from '../../../src/state/Store/reducer';
changed to
import Store from '../../../src/state/Store';