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
Related
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
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.
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.
I'm currently witting an application using reactjs utilizing react-router, redux, thunk and firebase among other technologies. Unfortunately I'm getting the following error when I dispatch an actions which is asynchronous.
I went through all the similar questions suggested by stackoverflow and still can't find the problem.
Any help would be appreciated.
Error:
app.js:54628 Error: Actions must be plain objects. Use custom middleware for async actions.
at dispatch (app.js:31576)
at Object.fetchBase (app.js:32220)
at Object.next (app.js:63640)
at app.js:54507
at app.js:54622
at
package.json
...
"react-router-dom": "^4.1.1",
"react-router-redux": "^4.0.8",
"redux-thunk": "^2.2.0",
"redux": "^3.7.1",
...
store.js
import { createStore, applyMiddleware, compose } from 'redux';
import reduxThunk from 'redux-thunk';
import { routerMiddleware } from 'react-router-redux';
import { browserHistory } from 'react-router-dom';
// Import the root reducer
import allReducers from './reducers';
// import default data
import config from './data/config';
const defaultState = {
events: [],
config,
activeGame: null,
basePath: '',
};
const router = routerMiddleware(browserHistory);
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(allReducers, defaultState,
composeEnhancers(applyMiddleware(reduxThunk, router)));
export default store;
Actions.js
...
export const fetchBase = () => {
const organizationRef = database.ref(`users/${auth.currentUser.uid}/organization`);
return dispatch => {
organizationRef.once('value', snapshot => {
dispatch({
type: ActionTypes.FETCH_BASE,
payload: snapshot.val()
});
});
};
}; // fetchBase
...
I think organizationRef.once returns firebase.Promise that is why you got that error.
Your action creator looks pretty good, but try to get ref to firebase database like this:
firebase.database().ref(`/users/${uid}/employees`)
Your action creator looks fine. The error message indicates, that you're not registering the redux-thunk middleware correctly.
Try to import it as followed:
import thunk from 'redux-thunk';
and then apply it with
const store = createStore(allReducers, defaultState,
composeEnhancers(applyMiddleware(thunk, router)));
Got this from: redux-thunk repo
Thanks everybody for all your help. I did find the issue and am kicking myself. The code above works fine, the problem is where I registered the store. I was importing an auxiliary store file which I was using for testing purposes. Once I updated the real store, I forgot to switch to it.
I am new to React with Redux structure and its concept. In my application, I need to navigate specific path in an action after login. My action code is:
const login = (resp:Object) => (dispatch: any) =>{
// api call
if(apiCall is success){
window.location.href = "http://localhost:3000"+localStorage.getItem("pathBeforeLogin");
}
else{
window.location.href = "http://localhost:3000/login";
}
});
}
This code is working fine but my senior asked me to do this work without using window.location.href. As We are using the react-router v4, browserHistory.push is also not working.
react-router-redux provides a Redux middleware that allow you to navigate via Redux actions.
From https://github.com/reactjs/react-router-redux:
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { routerMiddleware, push } from 'react-router-redux';
// Apply the middleware to the store
const middleware = routerMiddleware(browserHistory);
const store = createStore(
reducers,
applyMiddleware(middleware)
);
// Dispatch from anywhere like normal.
store.dispatch(push('/foo'));
So in your example, instead of assigning strings to window.location.href, you'd do the following:
store.dispatch(push('/login'));