redux toolkit +connected-react-router - reactjs

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.

Related

Uncaught Could not find router reducer in state tree, it must be mounted under "router"

React router works normal. But if I add <Redirect> or call from <Link>, I have this exception:
Uncaught Could not find router reducer in state tree, it must be mounted under "router"
rootReducer.js:
import { combineReducers } from 'redux';
import { connectRouter } from 'connected-react-router';
import counterReducer from './Counter/counter.reducer';
import sidebarReducer from './Sidebar/sidebar.reducer';
export default (history) => combineReducers({
router: connectRouter(history),
counter: counterReducer,
sidebar: sidebarReducer,
});
store.js:
import { createBrowserHistory } from 'history';
import { applyMiddleware, compose, createStore } from 'redux';
import { routerMiddleware } from 'connected-react-router';
import createRootReducer from './rootReducer';
export const history = createBrowserHistory();
export default function configureStore(preloadedState) {
const composeEnhancer = compose
const store = createStore(
createRootReducer(history),
preloadedState,
composeEnhancer(
applyMiddleware(
routerMiddleware(history),
),
),
);
if (module.hot) {
module.hot.accept('./rootReducer', () => {
store.replaceReducer(createRootReducer(history));
});
}
console.log(store.getState());
return store;
}
Check the history documentation. If you're using react-router-dom#5.x.x then you should use history#4.10.1 because the latest version of history (v5) only works with react-router-dom#6.x.x

How to apply async react redux middleware

I'm a beginner in react.
I'd like to use the react redux to request api.
Error: Actions must be plain objects. Use custom middleware for async actions. An error has occurred.
Please help me with any problems.
I'd like to ask you how redux middleware should be applied.
action/index.js
export const fetchActionMovies = async () => {
const request = await axios.get(`${BASE_URL}/discover/movie?api_key=${API_KEY}&with_genres=28`)
return {
type: FETCH_ACTION_MOVIES,
payload: request
}
}
reducers/reducerActionMovies.js
import { FETCH_ACTION_MOVIES } from '../actions/index';
export default function (state = {}, action) {
switch (action.type) {
case FETCH_ACTION_MOVIES:
const data = action.payload.data.results;
return { ...state, data }
default:
return state;
}
}
container/ActionMovie.jsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchActionMovies } from '../store/actions/index';
const ActionMovies = () => {
const dispatch = useDispatch();
const fetch = dispatch(fetchActionMovies());
console.log(fetch);
return (
<div>
<h1>Action Movies</h1>
</div>
)
}
export default ActionMovies;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import ReduxThunk from 'redux-thunk';
import rootReducer from './store/reducers';
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(ReduxThunk))
);
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
Error: Actions must be plain objects. Use custom middleware for async actions.
First, even if you're going to use redux-thunk, you need to break up your action into three parts to track the asynchronous state of the request.
const FETCH_ACTION_MOVIES_REQUEST = "FETCH_ACTION_MOVIES_REQUEST";
const FETCH_ACTION_MOVIES_SUCCESS = "FETCH_ACTION_MOVIES_SUCCESS";
const FETCH_ACTION_MOVIES_FAILURE = "FETCH_ACTION_MOVIES_FAILURE";
You should create three actions that use these types that your reducer will track. Now, if you're not going to use redux-thunk... you will need to perform this fetch in your component. However, if you are using redux-thunk you can create an action like this:
export const fetchActionMovies = () => dispatch => {
dispatch(fetchActionMoviesRequest());
return axios.get(`${BASE_URL}/discover/movie?api_key=${API_KEY}&with_genres=28`).then(({
data
}) => {
dispatch(fetchActionMoviesSuccess(data));
}).catch(error => {
dispatch(fetchActionMoviesFailure(error));
})
}
Another option to consider is redux-saga.

store.getState is not a function Redux-persist

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';

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.

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