connect multiple combined reducers in redux with conditional - reactjs

I have an app that have different access level, like admin, guest and normal user, so it has several roles. My folder structure is very clear, some components are shared like Button, Loader but reducers and actions are not shared, because they are completely different of apps.
I did this to do conditioning setting for my store (Yes I only need one store because the entry for all type of the user is the same)
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { getUserRole } from './utils' // merely localstorage util to get roles
import { composeWithDevTools } from 'redux-devtools-extension'
import userReducers from './reducers'
import adminReducers from './reducers/admin'
//share reducer btw member and admin
let reducers
if (getUserRole() === 'member') {
reducers = userReducers
} else {
reducers = adminReducers
}
console.log('reducers', reducers) //undefined during the first load, most likely store is done setup before localstorage?
const store = createStore(
reducers,
composeWithDevTools(
applyMiddleware(thunk)
)
)
export default store
The problem is reducers is undefined unless I refresh the entire page.

Maybe the problem is that localStore is not async according to this SO answer.
So by returning a Promise you'll make sure getUserRole() is not undefined:
export function getUserRole(){
return new Promise((resolve, reject) => {
let role = localStorage.getItem('token').decode('role')
resolve(role)
})
}
and in index.js:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { getUserRole } from './utils' // merely localstorage util to get roles
import { composeWithDevTools } from 'redux-devtools-extension'
import userReducers from './reducers'
import adminReducers from './reducers/admin'
//share reducer btw member and admin
let store
getUserRole().then(role => {
reducers = role === 'member'
? userReducers
: adminReducers
console.log('reducers', reducers)
store = createStore(
reducers,
composeWithDevTools(
applyMiddleware(thunk)
)
)
})
export default store
Tell me if something went wrong.

Related

Actions must be plain objects. Use custom middleware for async actions error eventhough useing thunk as middleWare

I got this error:
"Actions must be plain objects. Use custom middleware for async actions."
even though i use thunk as a middleWare.
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import { allReducers } from './reducers';
import thunk from 'redux-thunk';
import { getAllCourses } from './../../utils/courseServices';
export const store = createStore(allReducers, composeWithDevTools(
applyMiddleware(thunk),
// other store enhancers if any
));
//initiliaze
store.dispatch(getAllCourses())
//subscribe
store.subscribe(()=>console.log(store.getState()))
and also my action:
import { getAllCourses } from "../../utils/courseServices"
export const courseAction = () =>{
return async dispatch =>{
//fetching data from server
const {data} = await getAllCourses()
await dispatch({type:"INIT" , payload: data.courses})
}
}
You are dispatching getAllCourses there, not courseAction. That's probably your problem.
Also, please be aware that in new code you should be using configureStore of #reduxjs/toolkit, not createStore. Modern Redux does not use switch..case reducers, ACTION_TYPES, immutable reducer update logic, hand-written action creators or connect/mapStateToProps. (And that is nothing new, but the recommendation since 2019).
You are probably following an outdated tutorial - please follow the official Redux tutorial

How would I add stack tracing to my current Redux Devtools setup?

Here is my current redux store assignment:
import { composeWithDevTools } from 'redux-devtools-extension';
import { createStore, applyMiddleware } from 'redux';
import { PersistGate } from 'redux-persist/integration/react';
import reduxThunk from 'redux-thunk';
const persistedReducer = persistReducer(persistConfig, reducers);
const store = createStore(
persistedReducer,
composeWithDevTools(applyMiddleware(reduxThunk)),
);
I'd like to be able to stack trace within my project using Redux Devtools. The examples given don't really match my setup and was just wondering how I would go about in adding something like the trace property without totally rewriting my variable assignment to match theirs.
Have you tried setting options to composeWithDevTools and then dropping that into createStore?
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
const composeEnhancers = composeWithDevTools({trace: true}); // <<< set options here
const store = createStore(
reducer, /* preloadedState, */
composeEnhancers( // <<< use it here
applyMiddleware(...middleware),
)
);

Using redux-thunks in Cypress

I have a React app built using Create React App and Redux with redux-thunk which I want to test using Cypress. When dispatching thunks from Cypress (which I do when setting up tests) the dispatched actions in the thunk are not propagated to the intended store.
To highlight what I mean, here is some code. Consider a simple store
import { applyMiddleware, compose, createStore } from "redux";
import thunk from "redux-thunk";
import { v4 as uuid } from "uuid";
import rootReducer from "./rootReducer";
const middleware = [thunk];
const store = createStore(rootReducer, applyMiddleware(thunk));
export { store };
if (window.Cypress) {
window.testId = uuid();
window.store = store;
}
and some thunk
const myThunk = () => dispatch => {
console.log(window.testId);
dispatch({type: "RANDOM_ACTION"});
}
If dispatch this action inside Cypress via
cy.window().its("store").invoke("dispatch", myThunk());
the logged testId will not be the same as if running console.log(window.testId) in the dev console in the browser.
Do anyone know have an idea how to approach this situation?
Thankful for advice

redux-persist not updating local storage even after successful dispatch

In my app on log out I'm doing this.props.dispatch(setUserSession({})) even passed null here. After dispatching in redux-logger I can see that it's changed to
action {type: "SET_USER_SESSION", userSession: {}}
next state {userSession: {}
But in local storage, I can still see the userSession that was there before dispatching null.
On what action or when will the persisted store will get updated.
I'm setting userSession to null on logout, but when the user refreshes the page, he is back in without login since the token is present in the store.
And also I don't want to do a purge or flush the full store, just that userSession key.
Current store configuration
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import reducer from './reducers';
let middleware = [thunk];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const configureStore = composeEnhancers(applyMiddleware(...middleware))(createStore);
const config = {
key: 'root',
storage
};
const combinedReducer = persistReducer(config, reducer);
const store = configureStore(combinedReducer);
const persistor = persistStore(store);
export { persistor, store };
Kindly provide some help, correct me if I'm missing something.

Configuration store variation with React/redux and thunkMiddleware

I've been through many examples to understand how Redux-Thunk works and most the time the store is configured in a various way. I guess there's the old way or the new way but I'm kind of lost. Here is three pattern that I've identified. If someone can explain me the differences between them :
The simple way :
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
const loggerMiddleware = createLogger()
const store = createStore(rootReducer, applyMiddleware( thunkMiddleware, loggerMiddleware));
the Official Reddit Async Exemple way (here) :
import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import createLogger from 'redux-logger'
const loggerMiddleware = createLogger()
export default function configureStore(preloadedState) {
return createStore(
rootReducer,
preloadedState,
applyMiddleware(
thunkMiddleware,
loggerMiddleware
)
)
}
the old way ?
import {compose, createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
const createAppStore = compose(
applyMiddleware(thunkMiddleware)
)(createStore);
export default function configureStore(initialState){
const store = createAppStore(rootReducer, initialState);
return store;
};
From there I have at least four questions :
Do we still need to use compose ? I only find it in "old" exemple ?
Is there any differences between import ReduxThunk from
'redux-thunk' and import thunkMiddleware from 'redux-thunk' ?
Does the simple way is also correct ?
I don't understand the preloadedState pattern from the Reddit Async Exemple.
thanks.
The signature for createStore is createStore(reducer, [preloadedState], [enhancer]).
preloadedState is an initial state you load before you initialize your app. For instance, if you prerender your first page on a server and want to pass app state as a JSON inside your HTML. Sometimes you need to fetch this state asynchronously that is where the second example is useful. You fetch your state and create a store using that state in the callback of your ajax call.
The third argument.
enhancer is a higher-order function that composes a store creator to return a new, enhanced store creator. applyMiddleware is a store enhancer shipped with redux. If you want to combine multiple store enhancers you need to use compose function.
For instance, redux-devtools-extension for chrome is an enhancer so to use it in your app you would need compose function
let store = createStore(reducer, initialState, compose(
applyMiddleware(...middleware),
window.devToolsExtension ? window.devToolsExtension() : f => f
));
When you import something from 'redux-thunk' you use default export so you can name your variable as you want. It doesn't matter.
A simple way is also correct if you don't need anything fancy it would work just fine.

Resources