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

Viewed 4536

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!

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.

Related