making a post request inside mapDispatchToProps in react-redux - javascript

I'm trying to make a post request inside a function whenever i click on a button.
here is the code of the button
<Button onClick={handleClick}>Add to Cart</Button>
and here is the `handleClick funcion:
const handleClick = (event) => {
event.preventDefault();
props.postCart(itemData.product_name, itemData.product_price);
}
and here i showcase the code of mapDispatchToProps function:
const mapDispatchToProps = dispatch => {
return {
postCart: (productName, productPrice) => dispatch(postAddToCart(productName, productPrice))
}
}
finally the code of postAddToCart:
export const postAddToCart = (productName, productPrice) => {
const email = sessionStorage.getItem('email');
return (dispatch) => {
dispatch(productName, productPrice);
//REST API endpoint
axios.post('http://localhost:8000/api/auth/add-to-cart', {
email:email,
})
.then(resp => {
dispatch({
type: actionTypes.ADDED_TO_CART,
status: resp.status
});
})
.catch(resp => {
dispatch({
type: actionTypes.ADDED_TO_CART,
status: "FAILED"
});
})
}
}
But whenever i click the button Add to cart i get the following error:
Error: Actions must be plain objects. Use custom middleware for async actions.
knowing that i'm using the redux-thunk middleware.
Can you please tell me what's the problem and how can i fix it ? thank you ^^. if i missed something tell me in comments to add it ^^

Your function postAddToCart() returns a "dispatcher", i.e. a function that expects dispatch as an argument.
The error is that you are trying to dispatch this "dispatcher", instead of an "action":
// wrong: calling 'dispatch()'
postCart: (productName, productPrice) => dispatch(postAddToCart( ... ))
// correct: calling the returned dispatcher and pass 'dispatch' as argument
postCart: (productName, productPrice) => postAddToCart( ... )(dispatch)

Related

how to catch error in redux action and display the error message in UI

I have a redux action called academyRedirect.js which is invoked whenever an user is redirected to the path /user/academy when he pushes the 'academy' button in my main page.
export const getAcademyAutoLogin = () => {
axios.get(`/user/academy`)
.then((response) => {
window.location.replace(response.data); // this is replaced by an URL coming from backend
})
.catch((error) => {
reject(error.response);
});
return null;
};
Whenever the user is not allowed to access the academy (for not having credentials) or whenever i get an error 500 or 404, i need to display a modal or something to inform the user that an error occurred while trying to log into the academy. Right now im not being able to do it, the page just stays blank and the console output is the error.response.
Any help is appreciated
Redux Store
export const messageSlice = createSlice({
name: 'message',
initialState: { isDisplayed: false, errorMessage: ''},
reducers: {
displayError(state, action) {
state.isDisplayed = true
state.errorMessage = action.message
},
resetErrorState() {
state.isDisplayed = false
state.errorMessage = ''
},
}
})
export const messageActions = messageSlice.actions;
Inside the component:-
const Login = () => {
const errorState = useSelector(globalState => globalState.message)
const onClickHandler = async => {
axios.get(`/user/academy`)
.then((response) => { window.location.replace(response.data) })
.catch((error) => {
dispatch(messageActions.displayError(error))
});
}
return (
{errorState.isDisplayed && <div>{errorState.errorMessage}</div>}
{!errorState.isDisplayed &&<button onClick={onClickHandler}>Fetch Data </button>}
)
}
Maybe this is of help to you
You can try to add interceptor to your axios.
Find a place where you create your axios instance and apply an interceptor to it like this
const instance = axios.create({
baseURL: *YOUR API URL*,
headers: { "Content-Type": "application/json" },
});
instance.interceptors.request.use(requestResolveInterceptor, requestRejectInterceptor);
And then, in your requestRejectInterceptor you can configure default behavior for the case when you get an error from your request. You can show user an error using toast for example, or call an action to add your error to redux.
For second case, when you want to put your error to redux, its better to use some tools that created to make async calls to api and work with redux, for example it can be redux-saga, redux-thunk, redux-axios-middleware etc.
With their docs you would be able to configure your app and handle all cases easily.

React-js error undefined "type" property

I want to logout the user immediately after login , so I could see in redux if It works and I get this error: Uncaught Error: Actions may not have an undefined "type" property. Have you misspelled a constant?
I should get AUTH_LOGOUT action in my redux after the success- example image.
As I understand the error Is in the checkAuthTimeout:
export const checkAuthTimeout = (expirationTime) => {
return dispatch => {
setTimeout(() => {
dispatch(logout())
}, expirationTime )
}}
My logout:
export const logout = () => {
return {
type: actionTypes.AUTH_LOGOUT
}}
And auth:
export const auth = (email, password) => {
return dispatch => {
dispatch(authStart());
const authData = {
email:email,
password:password,
returnSecureToken: true
}
axios.post('https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=AIzaSyCpqBy-KjAJWCMUYLHVWAIu_HWZd3yzHVE', authData)
.then(response => {
console.log(response);
dispatch(authSuccess(response.data.idToken, response.data.localId));
dispatch(checkAuthTimeout(response.data.expiresIn));
})
.catch(err => {
dispatch(authFail(err.response.data.error));
});
}}
The error itself is pretty clear, your action type has value of undefined. Meaning actionTypes.AUTH_LOGOUT is undefined.
Try do a console.log on actionTypes.AUTH_LOGOUT, probably it's not defined or not imported properly. Show us the file where it is defined and imported, but I am positive the problem is either on how you exported or imported it.

Edit/Update Action Creator in Redux

export function editPost(props){
const request = axios.put(`http://www.example.com/posts`, props) ;
return{
type: EDIT_POST,
payload: request
};
}
hi,, is this what a proper "Update" action should look like in Redux?
using axios to make the request
the type was created in another file and then imported
Thanks
axios is promise-based, so currently you're returning a payload that doesn't exist. Look for redux-thunk and use it in the following way:
actionCreator() {
return (dispatch) => {
return axios.put('url').then((res) => dispatch({ type: EDIT_POST, payload: res }))
}
}

Appending Props in React-Redux Form using Axios

I have an action that returns dispatch. Right now i am posting props... but i'd like to add a few peices like (uname, role, etc) to the request. What is he easiest way to do so?
I thought something like:
const addlFields = { username: 'newTester', role: 'moderator' }
Any good advice on how to get this done? Im assuming it is not an axios issue since axios and accepting the props obj.
My request
const sendMessageRequest = axios.post(`${POST_MSG_URL}`, props)
.then(response => {
return dispatch => {
dispatch(
{ type: POST_MESSAGE,
payload: sendMessageRequest
}
);
}
});`

How do we mock fetch in Redux Async Actions?

In the Writing Tests section of Redux,http://rackt.org/redux/docs/recipes/WritingTests.html, how does the store.dispatch(actions.fetchTodos()) not invoke the fetch method, if store.dispatch is literally calling actions.fetchTodos?
it('creates FETCH_TODOS_SUCCESS when fetching todos has been done', (done) => {
nock('http://example.com/')
.get('/todos')
.reply(200, { todos: ['do something'] })
const expectedActions = [
{ type: types.FETCH_TODOS_REQUEST },
{ type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } }
]
const store = mockStore({ todos: [] }, expectedActions, done)
store.dispatch(actions.fetchTodos())
})
Everytime I try to run something similar to this, I keep getting a fetch is not defined. Even if I use nock. So I have to spy my action to not get the call to fetch.
Here is my unit test:
it('should request a password reset, and then return success on 200', (done) => {
nock('http://localhost:8080/')
.post('/password-reset-requests')
.reply(200);
var email = "test#email.com";
const expectedActions=[
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
];
const store = mockStore({}, expectedActions, done);
store.dispatch(Actions.addPasswordResetRequest());
here is the action:
export default function addPasswordResetRequest(email){
return dispatch => {
dispatch(requestAddPasswordResetRequest(email));
return addPasswordResetRequestAPI(email)
.then(() =>{
dispatch(requestAddPasswordResetRequestSuccess());
})
.catch((error) => {
dispatch(requestAddPasswordResetRequestFailure(error));
});
};
}
and the function that calls fetch:
export const addPasswordResetRequestAPI = (email) => {
return fetch(
SETTINGS.API_ROOT + '/password-reset-requests',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: email,
code: NC_SETTINGS.GROUP.code
})
}
)
.then(handleResponse);
};
I'm not sure if the way I am doing is sufficient for the purpose of just testing actions, but then I do run into the problem of store.dispatch only returning the first element of expectedActions, and it doesn't equal the list I supply in the spied addPasswordResetRequest. Below includes the spied action.
it('should request a password reset, and then return success on 200', (done) => {
nock('http://localhost:8080/')
.post('/password-reset-requests')
.reply(200);
Actions.addPasswordResetRequest = spy(() => {
return ([
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
]
);
});
var email = "test#email.com";
const expectedActions=[
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST},
{type: REQUEST_ADD_PASSWORD_RESET_REQUEST_SUCCESS}
];
const store = mockStore({}, expectedActions, done);
store.dispatch(Actions.addPasswordResetRequest());
The action "addPasswordResetRequest" isn't an action per-say.
it's a composite action with 3 sub-actions
startAction =requestAddPasswordResetRequest,
successAction =requestAddPasswordResetRequestSuccess
failAction =requestAddPasswordResetRequestFailure
I generally tests each action separately. so i would have something like
describe("requestAddPasswordResetRequest", () => {
it("shows the loading spinner or whatever", ...);
it("does some other state change maybe", ...);
});
describe("requestAddPasswordResetRequestSuccess", () => {
it("hides the loading spinner or whatever", ...);
it("changes the password state or something", ...);
});
describe("requestAddPasswordResetRequestFailure", () => {
it("hides the loading spinner or whatever", ...);
it("shows the error somehow", ...);
});
//each test would be something like
it("changes the password state or something", ()=>{
const action = requestAddPasswordResetRequestSuccess({
some : "payload from the server"
});
const newState = myReducer({ state : "somestate" }, action);
expect(newState).to.be.eql("expected result for that action");
});
Notice how in the test i don't need the store or any async logic. thats the beauty of redux (and functional stuff in general), it's simple :)
after this i would have a separate test for the whole thing and make sure that the correct simple actions get dispatched by the composite action, in which i would mock everything (including store and the "fetch" thing, since i just want to test that the actions get fired in the correct order).
if the actions are dispatched in the correct order and each action work separably i would be pretty confident that the thing works as expected.
Hope this helps.

Categories

Resources