Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Redux correct way to handle error / success messages unique to each component

Problem outline

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.


What I came up with so far

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.

Question

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!

like image 258
linasmnew Avatar asked Nov 03 '17 18:11

linasmnew


People also ask

How do you handle errors in React Redux?

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.

What are the ways to handle errors in React?

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.

How do you handle error in Redux thunk?

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.


1 Answers

1.

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...

2.

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.

like image 110
Vincent Lecrubier Avatar answered Nov 01 '22 19:11

Vincent Lecrubier