store.getState is not a function Redux-persist - reactjs

I'm trying to implement Redux-persist on my React Native app. Following the setup docs exactly, I changed my store.js from this:
import { applyMiddleware, createStore } from 'redux';
import * as thunkMiddleware from 'redux-thunk';
import reducers from '../reducers';
let middlewares = [thunkMiddleware.default];
const store = createStore(reducers, applyMiddleware(...middlewares));
export default store;
To this:
import { applyMiddleware, createStore } from 'redux';
import * as thunkMiddleware from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist';
import AsyncStorage from '#react-native-community/async-storage';
import reducers from '../reducers';
const persistConfig = {
key: 'root',
storage: AsyncStorage,
};
const persistedReducer = persistReducer(persistConfig, reducers);
let middlewares = [thunkMiddleware.default];
export default () => {
let store = createStore(persistedReducer, applyMiddleware(...middlewares));
let persistor = persistStore(store);
return { store, persistor };
};
But now, I'm getting the error TypeError: store.getState is not a function (In 'store.getState()', 'store.getState' is undefined).
Note: I've checked out many questions on stackoverflow with the same store.getState error, but they have very specific issues different from my setup.
Edit: Provider implementation (using RNNv2)
import { Navigation } from 'react-native-navigation';
import { Provider } from 'react-redux';
import store from '../../shared/redux/store';
import { registerScreens } from '../view/screens';
import { initialize } from './navigation';
/**
* Register screens and components for react native navigation
*/
registerScreens({ store, Provider });
const app = () => {
Navigation.events().registerAppLaunchedListener(() => {
initialize();
});
};
export default app;
Registerscreens:
const registerComponentWithRedux = (redux: any) => (
name: string,
component: any,
) => {
Navigation.registerComponentWithRedux(
name,
() => component,
redux.Provider,
redux.store,
);
};
export function registerScreens(redux: any) {
registerComponentWithRedux(redux)(screen, screen.default);
...
}

The issue is with the export, you are exporting a function that returns store in a key. So if you update the store reference in register screens it will work.
import returnStoreAndPersistor from '../../shared/redux/store';
const {store} = returnStoreAndPersistor();
registerScreens({ store, Provider });

If u use 'redux-persists' and return{store, persistor} from ../store just try THIS!!!!!
import returnStoreAndPersistor from "../../store";
const { store } = returnStoreAndPersistor()
console.log(store.getState())

For anyone who has this issue & is using Typescript where the only property on "getState()" is "_persist", the problem was for me caused by setting the type of your combined reducers to "any" due to the type error on supplying your store to persistStore:
Type AnyAction is not assignable to type {your action type}
When I fixed the above issue, the getState issue was resolved.
I fixed this by doing this:
const _ = (state: any = 0, _: AnyAction) => state;
const root = combineReducers({
_,
applicationStatusReducer,
...
});
It seems to resolve the issue (albeit in a hacky / not actually resolving the underlying issue kind of way), if you just add an empty reducer with the action type of "AnyAction" to it.

This issue is happening because this export default method instead of doing this you
can do it like this.
export let store = createStore(persistedReducer, applyMiddleware(...middleware));
export let persistor = persistStore(store);
sorry for any inconvenience

const store = createStore(persistedReducer, applyMiddleware(...middlewares));
const persistor = persistStore(store);
export { store, persistor };
and then
import { store, persistor } from '../../shared/redux/store';

Related

redux toolkit +connected-react-router

I'm trying to set up redux with connected-react-router, but I didn't really understand how to do that, I'm using the create-react-app --template redux, but if I try to do that I get the following error: Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
import { configureStore, combineReducers } from "#reduxjs/toolkit";
import { connectRouter, routerMiddleware } from "connected-react-router";
import { createBrowserHistory } from "history";
import homePageSlice from "./reducers/homepage/homePageSlice";
export const history = createBrowserHistory();
export const store = configureStore({
reducer: {
router: connectRouter(history),
homepage: homePageSlice,
},
middleware: [routerMiddleware(history)],
});
setting middleware property will override the default middleware that configureStore injects.
Use this:
middleware: [
actionToPlainObject,
routerMiddleware(history)
]
and create actionToPlainObject.ts
import { Middleware } from '#reduxjs/toolkit';
import IStoreState from '../IStoreState';
export const actionToPlainObject: Middleware<IStoreState, any> = (store) => (
next
) => (action) => {
if (!action) {
return;
}
return next({ ...action });
};
If using class instances this allows to be passed to Redux.

Why is Redux-Persist not persisting the store

Hi there everyone I was messing around with react(redux to be precise) and came across a problem. When I refresh the page redux store does as well everything returns to its initial value, I googled the problem and found out that I have to use redux-persist. However even that is not working, I think the problem is with how I configured redux-persist but I could be wrong.
The code below is how I went about the redux-persist configuration.
// configureStore.js
import { createStore, applyMiddleware } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import rootReducer from "../reducers/index";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
const persistConfig = {
key: "root",
storage,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(
persistedReducer,
composeWithDevTools(applyMiddleware(thunk))
);
export const persistedStore = persistStore(store);
The code below shows how I went about making the rootReducer.
// index.js
import { combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";
import authReducer from "./auth";
import messageReducer from "./message";
import chatroomReducer from "./chatroom";
import { LOGOUT_SUCCESS } from "../actions/authTypes";
const apiReducer = combineReducers({
authReducer,
messageReducer,
chatroomReducer,
form: formReducer,
});
const rootReducer = (state, action) => {
if (action.type === LOGOUT_SUCCESS) {
state = undefined;
}
return apiReducer(state, action);
};
export default rootReducer;
And the code below is the index.js that comes when creating the react app.
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { PersistGate } from "redux-persist/integration/react";
import { Provider } from "react-redux";
import { store, persistedStore } from "./Redux/configureStore";
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistedStore}>
<React.StrictMode>
<App />
</React.StrictMode>
</PersistGate>
</Provider>,
document.getElementById("root")
);
reportWebVitals();
Can anyone tell me what is wrong with my code? If you need additional info please let me know.
Try configuring the whitelist, where you must indicate which reducers to store.
Remember to import the reducers before adding them to the list
import navigation from "./reducers/navigation";
// WHITELIST
const persistConfig = {
key: 'root',
storage: storage,
whitelist: ['navigation'] // only navigation will be persisted
};
First, you need to import storage from Async
import AsyncStorage from '#react-native-community/async-storage';
Use WhiteList like this
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: [
'user'
],
};
"user" is the specific root reducer you want to store
As per this answer, you also need to clear out the redux-persist storage when you catch the LOGOUT_SUCCESS action.
First, you need to import the appropriate storage engine and then clear it out completely.
const rootReducer = (state, action) => {
if (action.type === SIGNOUT_REQUEST) {
// for all keys defined in your persistConfig(s)
storage.removeItem('persist:root')
// storage.removeItem('persist:otherKey')
return appReducer(undefined, action);
}
return appReducer(state, action);
};

Redux-Persist and Connected-React-Router: TypeError: Cannot read property 'location' of undefined

I am trying to get React app to work properly with React Router and Redux, but I constantly getting TypeError: Cannot read property 'location' of undefined to any components that have redux. I am using connected-react-router lib to register router in the store but apparently I did something wrong
store/configureStore.js
import { createStore, applyMiddleware, compose } from 'redux';
import reducer from '../reducers';
import { persistStore } from 'redux-persist';
import { createBrowserHistory } from 'history';
import { routerMiddleware } from "connected-react-router";
let _persistor;
let _store;
export const history = createBrowserHistory();
export const getStore = (props) => {
if (_store) {
return _store;
}
const initialState = (props) ? {...props} : {};
_store = createStore(
reducer,
initialState,
compose(
applyMiddleware(routerMiddleware(history)),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
),
);
_persistor = persistStore(_store);
return _store;
};
export const getPersistor = () => {
return _persistor;
};
reducers/index.js
import { combineReducers } from 'redux';
import { persistReducer } from 'redux-persist';
import Menu from './Menu';
import SelectedServices from './SelectedServices';
import { connectRouter } from 'connected-react-router'
import { history } from "../store/configureStore";
const rootReducer = (history) => combineReducers({
router: connectRouter(history),
menu: Menu,
selectedServices: SelectedServices,
});
const reducer = persistReducer(
{
key: 'root',
storage: require('localforage'),
whitelist: [],
},
rootReducer(history),
);
export default reducer;
After three hours of debugging and reading more about each library of connected-react-router, react-redux, and react-router-dom I found one line which I skip several times, but eventually I checked it in my package.json. At current moment:
v6.0.0 requires React v16.4.0 and React Redux v6.0. (It does not support React Redux v7.0 yet. We're looking into the compability issue.)
I was using react-redux: v7.0.1 which a real facepalm to my self. So I downgraded to v6 and everything start works fine.

How to get store from redux-persist on app load in react-native?

I have a problem running react-native app. I've got a store that uses redux-persist and some other middleware and the problem is when the app loads, data from the store is not there, only the default values. However as soon as I touch any button the data is there, so the problem is I can't delay rendering until the store is rehydrated. I've tried options with wrapping the app with PersistGate with both loading and bootstrapped values, however this does not work for me. The current code is:
Store config:
import { createStore, applyMiddleware, compose } from 'redux';
import promise from 'redux-promise-middleware';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist';
import { AsyncStorage } from 'react-native';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import rootReducer from './reducers';
const middleware = [
promise(),
thunk,
];
if (__DEV__) { // eslint-disable-line
// middlewares.push();
}
const enhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; // eslint-disable-line
const persistConfig = {
key: 'root',
storage: AsyncStorage,
stateReconciler: autoMergeLevel2,
};
const pReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(pReducer, {}, enhancers(applyMiddleware(...middleware)));
export const persistor = persistStore(store);
Root Reducer:
import { combineReducers } from 'redux';
import loginScreenReducer from 'screens/login/LoginScreen.reducer';
export default combineReducers({
loginScreenReducer,
});
App:
import { AppLoading } from 'expo';
import React from 'react';
import { PersistGate } from 'redux-persist/integration/react';
import { Provider } from 'react-redux';
import Root from 'Root.component';
import { store, persistor } from 'reduxConfig/store';
export default class App extends React.Component {
state = { }
render() {
return (
<Provider store={store}>
<PersistGate persistor={persistor}>
{(bootstrapped) => {
if (bootstrapped) {
console.log(store.getState());
return <Root />;
}
return <AppLoading />;
}}
</PersistGate>
</Provider>
);
}
}
The component I'd like to make work correctly:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Navigator from './Navigator';
import { LoginScreen } from '../screens';
#connect(
state => ({
token: state.loginScreenReducer.token,
isLogged: state.loginScreenReducer.isLogged,
}),
)
export default class AppNavigator extends Component {
state = { }
render() {
const {
token,
} = this.props;
if (token) {
return <Navigator />;
}
return <LoginScreen />;
}
}
export const { router } = Navigator;
The component shows login screen first but if I touch any button on it, it renders Navigator component. Also when I get the state of the store (App, before returning <Root />, it has the next structure, containing empty object, which is currently default for loginScreenReducer:
The app works correctly except for redux-persist.
===== New Answer =====
The old answer is wrong, the problem came back later. It looks like it's a problem with redux-persist, or even expo, react-native-scripts or whatever, I was not been able to locate the problem source. The behavior I described before comes from redux-persist inability to access AsyncStorage. However the only thing that helps now is completely stopping expo and running it again, after that redux-persist is able to access AsyncStorage again.
It also looks like disabling live reload really helps with this problem if not stops it at all.
===== Old Answer =====
I've ended up by downgrading react-redux package to version 4.4.9, looks like this has solved the problem, otherwise the code I posted initially should be totally okay. The problem with auto-filling state on button press was caused my a couple of my own mistakes both from BE and FE sides, that lead to store be filled with different items. They are not in the given code. Also I've changed my store a little, I'm not sure if the initial problem was connected to enhancers combining. So now my store looks like this:
import { createStore, applyMiddleware } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';
import { AsyncStorage } from 'react-native';
import thunk from 'redux-thunk';
import promiseMiddleware from 'redux-promise-middleware';
import { createLogger } from 'redux-logger';
import { LOGOUT } from 'screens/login/LoginScreen.actions';
import appReducer from './reducers';
const middlewares = [
promiseMiddleware(),
thunk,
];
if (__DEV__) { // eslint-disable-line
middlewares.push(createLogger());
}
const rootReducer = (state, action) => {
if (action.type === LOGOUT) {
state = undefined;
}
return appReducer(state, action);
};
const persistConfig = {
key: 'root',
storage: AsyncStorage,
stateReconciler: autoMergeLevel2,
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(persistedReducer, undefined, applyMiddleware(...middlewares));
export const persistor = persistStore(store);
I hope this will help somebody.

action.rehydrate is not a function

I have been getting the following error since sometime in my console, i have no idea what it means and why it is originating. Please spread some light on this matter.
it says:
persistReducer.js:50 Uncaught TypeError: action.rehydrate is not a function
at _rehydrate (persistReducer.js:50)
at persistReducer.js:54
redux-persist version on package.json: "^5.6.11"
locked version: "5.9.1"
Store configuration code:
import thunk from 'redux-thunk';
import { persistStore } from 'redux-persist';
import { History, createBrowserHistory } from 'history';
import { createUserManager, loadUser } from "redux-oidc";
import { routerReducer, routerMiddleware } from 'react-router-redux';
import { createStore, applyMiddleware, compose, combineReducers, GenericStoreEnhancer, Store, StoreEnhancerStoreCreator, ReducersMapObject } from 'redux';
import * as StoreModule from './reducers';
import { ApplicationState, reducers } from './reducers';
import userManager from "./utils/userManager";
// Create browser history to use in the Redux store
const baseUrl = document.getElementsByTagName('base')[0].getAttribute('href')!;
const history = createBrowserHistory({ basename: baseUrl });
export default function configureStore(history: History, initialState?: ApplicationState) {
// Build middleware. These are functions that can process the actions before they reach the store.
const windowIfDefined = typeof window === 'undefined' ? null : window as any;
// If devTools is installed, connect to it
const devToolsExtension = windowIfDefined && windowIfDefined.__REDUX_DEVTOOLS_EXTENSION__ as () => GenericStoreEnhancer;
const createStoreWithMiddleware = compose(
applyMiddleware(thunk, routerMiddleware(history)),
devToolsExtension ? devToolsExtension() : <S>(next: StoreEnhancerStoreCreator<S>) => next
)(createStore);
// Combine all reducers and instantiate the app-wide store instance
const allReducers = buildRootReducer(reducers);
const store = createStoreWithMiddleware(allReducers, initialState) as Store<ApplicationState>;
loadUser(store, userManager);
// Enable Webpack hot module replacement for reducers
if (module.hot) {
module.hot.accept('./reducers', () => {
const nextRootReducer = require<typeof StoreModule>('./reducers');
store.replaceReducer(buildRootReducer(nextRootReducer.reducers));
});
}
const persistor = persistStore(store);
return { store, persistor };
}
function buildRootReducer(allReducers: ReducersMapObject) {
return combineReducers<ApplicationState>(Object.assign({}, allReducers, { routing: routerReducer }));
}
// Get the application-wide store instance, prepopulating with state from the server where available.
const initialState = (window as any).initialReduxState as ApplicationState;
export const { store, persistor } = configureStore(history, initialState);
if you are working on localhost and using redux-devtools try to check Access File Url checkbox on extension options.
you can manage your chrome extensions by typing chrome://extensions/ in the address bar or by going to the setting and chose extensions from left menu.
I get this error when using redux devtools, remove this and it should go away.
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
Your code is using API of an older redux-persist version.
Refer to Basic Usage for the updated API:
import { persistStore, persistReducer } from 'redux-persist'
const persistConfig = {
key: 'root',
storage,
}
const allReducers = persistReducer(persistConfig, buildRootReducer(reducers));

Resources