Below is a standard way for dispatching error / success messages, at least that's what the tutorials recommend:
{ type: 'FETCH_POSTS_FAILURE', error: 'Oops' }
{ type: 'FETCH_POSTS_SUCCESS', success: 'yay', response: { ... } }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
However, the problem I'm experiencing with this, is that these error / success messages will show inside EVERY component listening to the specific reducer that's handling these actions.
Example scenario:
Image you have a todo application which has three actions add, edit and delete however all three of those actions are performed from separate pages in terms of UI. And they're all handled by the same todo reducer.
Also all components that provide UI for triggering these actions are listening to this todo reducer via mapStateToProps to receive todo changes (not relevant in this example) and success & error messages:
function mapStateToProps(state) {
return {
success: state.todo.success,
error: state.todo.error,
};
}
{!!this.props.success && <p>{this.props.success}</p>}
{!!this.props.error && <p>{this.props.error}</p>}
Issue with above The problem is that there’s no way to distinguish between add todo errors, edit todo errors and delete todo errors.
So if add todo triggers an error this error will now also show up next to edit todo and delete todo, because all 3 actions are listening for the same: state.todo.error.
Below are the 2 solutions I came up with. Any feedback / critique as well as new suggestions would be more than welcome.
1: Global status reducer that handles errors / successes of each action
Setup a global status reducer that handles errors / successes of each action - keys on the reducer would consist of small specialised sub-reducers unique to each action, i.e.:
const status = combineReducers({
add_todo, // handles error/success for add_todo
edit_todo, // handles error/success for edit_todo
delete_todo, // handles error/success for delete_todo
…
update_bio, // handles error/success for update_bio
etc...
});
Then you can simply listen for these success / error messages in your components and render them as follows:
function mapStateToProps(state) {
return {
error: state.status.add_todo.error,
success: state.status.add_todo.success
};
}
{!!this.props.error && <p>{this.props.error}</p>}
{!!this.props.success && <p>{this.props.success}</p>}
etc...
function mapStateToProps(state) {
return {
error: state.status.edit_todo.error,
success: state.status.edit_todo.success
};
}
{!!this.props.error && <p>{this.props.error}</p>}
{!!this.props.success && <p>{this.props.success}</p>}
The downside of this is that it would force you to create a sub-reducer for each action that an application provides, which I'm not sure is a correct way of doing it?
2: Use locally unique error / success keys inside of each reducer that manages more than a single action
i.e. we could modify the todo reducer as follows:
ADD_TODO_FAILED:
return {...state, error_add_todo: action.error }
EDIT_TODO_FAILED:
return {...state, error_edit_todo: action.error }
DELETE_TODO_FAILED:
return {...state, error_delete_todo: action.error }
Then you can simply listen for these success / error messages in your components and render them as follows:
function mapStateToProps(state) {
return {
error: state.todo.error_add_todo,
success: state.todo.success_add_todo
};
}
{!!this.props.error && <p>{this.props.error}</p>}
{!!this.props.success && <p>{this.props.success}</p>}
etc...
function mapStateToProps(state) {
return {
error: state.todo.error_edit_todo,
success: state.todo.success_edit_todo
};
}
{!!this.props.error && <p>{this.props.error}</p>}
{!!this.props.success && <p>{this.props.success}</p>}
The downside of this is would be that you would have to make sure that each error / success key inside of a given reducer is locally unique inside of that reducer.
And it would also make referring to keys inside of stateMapToProps more verbose as instead of saying state.todo.error
your would have to refer to that error
by using the specific name that you gave it.
So my question is, based on all of the above, did I completely miss something out of the equation in my above observations, or were my observations correct in which case what is the standard way of achieving this desired outcome?
And are the 2 solutions I proposed legit or too naive for a real world application?
Thanks a lot!
Usually the best approach to error handling with redux is to have an error field in state that is then passed to an error component. The error component doesn't have to just display an error, it could also do side effects with useEffect . How the error is set/unset depends on your application.
Error handling with Error Boundaries — For class components. Error boundaries are the most straightforward and effective way to handle errors that occur within your React components. You can create an error boundary component by including the life cycle method componentDidCatch(error, info) if you use class component.
For situations where you want to explicitly apply error handling to a thunk that is nested but which will not execute within the error handling scope of the parent thunk, you can do this using the forceHandleError function.
Your first option is problematic, as each of your sub reducers would not have access to the global state (list of todos). I would like to know how you would code each of the sub reducers...
Your second option is relatively good. However, it shows a common red flag: when you start naming variables like state.todo.error_add_todo
and state.todo.error_edit_todo
, you should usually want to use an object instead, ending up with state.todo.error.add_todo
.
Actually I would even go further and group error
and success
into status
.
This means that you would reshape your state to have this shape:
{
todos:[{...todo1}, {...todo2}, ...todos]
status:{
add_todo: {success: true, error: false, message: "yay"}
remove_todo: {success: false, error: true, message: "nooo"}
}
}
So this would be the way I would fix your code. However, this seems a bit overkill to me, as this state shape allows you to represent and store any combination of simultaneous errors from different reducers, which is not trivial to display to the user.
In the case of error display, such precision is rarely needed, and more often than not, you would just imperatively trigger a popup or a toaster message when your action fails.
Also, you should probably have a look at redux-actions
and redux-promise-middleware
, this is how I handle my errors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With