I'm working on error handling w/ fetch's in redux. Do people typically write reducers/actions error handlers for every single request like the videos from redux.js.org? I was thinking about dispatching the same action w/ an error flag to handle errors, but I don't know if that's the right way to go about it.
In my experience, yes. I ordinarily have actions like GET_THINGS, GET_THINGS_SUCCESS, and GET_THINGS_ERROR, and then have a case statement in the reducer to handle each action. Most React devs I know do it the same well. In general, I would suggest following that pattern. That said, it does result in you having to handle a lot of GET actions and ERROR actions basically the same way, since you'll likely just be adding something like { Loading: true } or { Error: true } to the state...
For error handling and posting / putting data I use a generic way of raising a dialog for the user to indicate success or failure.
e.g. have a NoificationsAction
const NotificationAction = (open = false, message = '', status = 'success') => ({type: NOTIFICATION, payload: {open, message, status}})
and some dilaog component which raises a dialog or snackBar to display a message
Dispatch this when your request fails
myRequest()
.then((response) => {
if (response.status === 200) {
dispatch(projectsSuccess(response.data))
}
})
.catch((error) => {
dispatch(NotificationAction(true, `My Error Text : ${error}`, 'error'))
})
This way you reduce your boiler plate code in reducers
Related
I am trying to set up an error handler for a React Component SignupForm.js that is responsible for registering users. In particular, the handler I am focused on implementing involves handling instances of a supplied user email already in use.
Here is my POST request:
await axios
.post("http://localhost:8000/signup", {
name,
email,
password,
tokenValue,
})
.catch((error) => {
console.log("ERROR --> ", error);
//this.displayError(error);
this.setState(
{
err: true,
errcode: error.response.data.errcode,
errtext: error.response.data.errText,
},
() => {
console.log("setstate done");
}
);
})
.then((res) => {
if (res !== undefined) {
console.log(res.data);
}
});
The image below shows Chrome Dev Tools output:
As you can see, console.log("Error --> ", error); is indeed firing.
However, this.setState() doesn't seem to be firing. There are a few indications of this.
The SignupForm state, as observed using the React DevTools plugin, remains unchanged.
I have included a print statement at the very top of my render() function like so:
render() {
console.log(this.state);
return (
<div className="form-container">
<form
name="sign-up"
method="post"
className="form-sign-up"
despite the notion that setState() calls are supposed to trigger re-renders, the component state never gets printed to the console after the error being caught.
console.log("setstate done"); never fires, as the output is also missing from the DevTools console.
Things I have tried
I tried encapsulating the setState() call into a error handler function displayError(e), like so:
displayError = (e) => {
this.setState(() => ({
err: true,
errcode: e.response.data.errcode,
errtext: e.response.data.errText,
}));
};
This didn't seem to have any effect. I also tried calling displayError(e) after having called this.displayError = this.displayError.bind(this); in the component constructor, which similarly had no effect.
At this point, I'm not really sure what else to try.
Figured it out. Initially, I was quite set on trying to get my code posted online for you folks to take a look at. After getting to know CodeSandbox a bit, I thought I was going to need to learn Docker so I could compose the front-end and back-end while having a free MySQL DB hosted somewhere online.
This was discouraging to me, as I knew Docker might take a bit of time to learn and I wanted to get to the bottom of this with the least amount of hoops possible.
I took what #S.Marx said and I investigated. After thinking about what they said a little more, it seemed strange to me as well that I would put my catch block before my then block.
Sure enough, swapping those two blocks completely solved the issue. The state is now being changed with the following:
await axios
.post("http://localhost:8000/signup", {
name,
email,
password,
tokenValue,
})
.then((res) => {
if (res !== undefined) {
console.log(res.data);
console.log("Signup Successful!!");
}
})
.catch((error) => {
console.log("Signup Error! --> ", error);
//this.displayError(error);
this.setState(
{
err: true,
errcode: error.response.data.errcode,
errtext: error.response.data.errText,
},
() => {
console.log("setstate done");
}
);
});
Of course, now the component that is supposed to render the state change on-screen isn't showing the error message. That, however, is a separate issue.
I've seen numerous articles and live examples of handling errors occurring in redux-thunks by adding ACTION_FAIL/ACTION_SUCCESS and sometimes ACTION_ATTEMPT/ACTION_START. This is great and all, although, all cases I've seen seem to give the user a simple notification like "Oops, this went wrong. Try again!" and go home early.
The case I'm looking for is having a seamless dispatch-error-retry-success transition or at least a better (more maintainable) solution than adding additional action dispatches to every thunk.
An example would be a token expiration.
If the token is about to expire or the fail occurred because of an expired token (and logging out is not a good option) then refreshing the token and retrying the request could be a solution without making the user scratch his head.
I'd love to hear:
How you tackle redux-thunk fails?
Did you try other alternative solutions before the current one?
If yes, why did you switch?
If you can handle a failure automatically (like in the refresh token example you mentioned), you don't need to dispatch an extra action at all. You can simply retry within the thunk, then dispatch the success action if you recovered successfully.
function doRequest() {
return dispatch => {
makeRequest()
.catch(error => {
if (isUnauthorizedError(error)) {
return refreshToken().then(makeRequest);
}
throw error;
})
.then(
doRequestSuccess,
doRequestFailure
);
}
}
The only reason to dispatch an extra "retry" action would be if you would want to communicate the temporary failure in the user interface.
I have an asynchronous function in my redux actions and it returns an object like this in my reducer:
{
user: {},
fetching: false,
fetched, false,
error: null
}
So basically when I start calling the asynchronous function I update the redux state to fetching: true and if the result is successfully fulfilled then fetched:true and fetching:false
So I map the redux state to props using connect in react-redux and I added this in my component's render function:
if(this.props.fetched) {
// Go to next page
}
And it automatically goes to the next page once the data is fetched.
However, I have a problem with my error handling. When error changes from null to error then how do I handle the error handling on my react component. What I have right now is:
if(this.props.error != null) {
// popup error
}
But now I end up in a situation that next time I call my action it already has this.props.error assigned to an error and its not null which results it displaying the popup even if there is no error.
Do I have to reset my error everytime it is displayed or is there any better practice of doing this whole thing?
You can use the redux-catch middleware to capture the errors for Redux reducers and middlewares.
You can use something like,
import reduxCatch from 'redux-catch';
function errorHandler(error, getState, lastAction, dispatch) {
//dispatch ERROR action as you need.
}
const store = createStore(reducer, applyMiddleware(
reduxCatch(errorHandler)
));
And, display your Error Popup when you receive the ERROR action which is triggered from the redux-catch middleware. When you close the popup, dispatch an action which resets the error to null so that popup would not be displayed if there are no errors to display.
I had a similar problem when I had a modal continue to display old errors after I closed the modal. I solved it by dispatching a "resetErrors" action after I received a success through my callback.
I have not come across a better solution other than "reseting" it every time.
Meteor uses callbacks, but it seems like there is no 'wait' for them with Redux actions. So, if using something like Redux and having an action something like this:
export function loginWithPassword(email, password) {
return dispatch => {
Meteor.loginWithPassword(email, password, error => {
if (error) {
return dispatch({type: ASYNC_ERROR, data: error.reason})
});
}
}
... the action will complete and return before Meteor's call does (thus not re-rendering the UI if needed). I have scoured for hours trying to find the best way to deal with this to no avail. Based on what I have been able to find, it would seem the options are Meteor Futures or Promises, however I cannot find anything indicating which might be better or why.
I believe that Futures can only be server side and are less widely used than Promises, so Promises may be the better options based on that. I am not even sure if Futures is an option since actions are client side. But I am also not sure if Meteor will play nice with Promises given it's general sync nature.
Promises I believe could be either client or server. Assuming they were client, would it be better to put them in the actions or in the UI code dispatching the actions - or does it really matter?
Does anyone have any thoughts or insight into how best to handle something like this? A working example of one or the other (or both) would be great because I sure couldn't find one after hours of searching. Specifically it would be great to see an example of a simple Meteor login with password dispatched via Redux that displays a 'User not found" or some other 'async' error in the UI login form (the FIRST time).
The action above actually works, but it returns before Meteor is done returning the error, so the error is not displayed in the UI the initial time.
TIA!
P.S. There are a couple of examples out there that display the error in an alert window or console.log it - but that is not the same thing as updating/displaying it as a prop in the UI that dispatched the action. There are no prop re-renders tied to an alert or console.log. The whole idea is to show the error in the form the user is currently on (login form in this example case)
Using redux-thunk, you could write something like below. I think the trick here is knowing the status of the user object once it's logged in. One way of doing this is to use store.dispatch inside a Tracker.autorun:
// whenever the reactive data changes, we will dispatch the appropriate action
Tracker.autorun(() => {
if (Meteor.loggingIn()) {
return store.dispatch({
type: LOGGING_IN,
});
}
const user = Meteor.user();
store.dispatch({
type: SET_USER,
user,
}
});
export const login = ({ email, password }) => (dispatch) => {
Meteor.loginWithPassword(email, password, (error) => {
if (error) {
return dispatch({
type: LOGIN_ERROR,
error,
});
}
return dispatch({
type: LOGIN_SUCCESS,
});
});
};
You could imagine a user reducer that has a state like {user: {loggingIn: Boolean, authError: SomeErrorModel}}} or something like that
You could clear the authError on LOGGING_IN, and add it on LOGIN_ERROR
For any UI changes based on this reducer, just connect your react component with react-redux's connect()
Recently I did that for one app and I'd like to know if this would be considered a bad practice or if it is ok. Let's say I have a reducer listening for two actions:
switch (action.type) {
case 'PRE_FETCH_ACTION':
return Object.assign({}, state, {value: action.value, isAllowed: true})
case 'FETCHED_SUCCESS':
if (!state.isAllowed) {
throw Error('You did not dispatch PRE_FETCH_ACTION before fetching!');
}
return Object.assign({}, state, {data: action.data, isAllowed: false})
}
So the flow is:
I dispatch PRE_FETCH_ACTION
I make a fetch call to an external API
When the service response returns, it dispatches FETCHED_SUCCESS
If somebody tries to fetch data without dispatching the PRE_FETCH_ACTIONfirst, the code will throw an error.
Ok, so this works just fine. My concern is, as I said, if this would be considered a bad pattern. Why do I think so? Because the isAllowed piece of state is kinda internal to the reducer and it does not affect any component render method.
I think it's ok to have such "technical" flags in your app state.
In your case you want to ensure that actions are dispatched in a specific order, which defines a particular behavior of your app (for example, displaying a loader on the screen when data-fetching starts). That's why I think your "technical" flag eventually manages your UI.
To let your reducer "reduce only", you could make your check before dispatching, by testing your state in action creator. This pattern is detailed in Redux's doc about async actions. You would do it in a "thunk" action creator that you would call instead of calling sync action "fetchedSuccess", like this:
function toFetchedSuccess(data) { // "thunk" action creator
return (dispatch, getState) => {
const state = getState(); // current state
if(!state.isAllowed) {
console.error(
'You did not dispatch PRE_FETCH_ACTION before fetching!');
return Promise.resolve(); // nothing to do
} else {
return dispatch(fetchedSuccess(data));
}
};
}