Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux how to handle errors in reducer

I'm unsure how to handle errors in a redux reducer. When my API fetch returns data, I need to transform its structure and check various properties have been set on the resulting data. However, I cannot throw errors in the reducer as redux requires at least the previous state to be returned. How do I go about handling this?

Note: I'm using react-redux-toastr to handle errors, and have access to the error dispatcher in the component and action functions.

reducers/object-search.js:

case "FETCH_OBJECTS_FULFILLED": {
  // munge data into expected format

  const _allObjects = [];
  const data = action.payload;

  // if can't find the surveys property
  if (!data.hasOwnProperty("surveys")) {

    // - - How do I handle this within the reducer? - - 

    // this.handleAlert("error", "ERROR", " Cannot find a list of surveys. Please contact support.");
    return {
      ...state,
      fetching: false
    }
  }

  // transform data (omitted for brevity's sake)

  return {
    ...state,
    fetching: false,
    fetched: true,
    options: _allObjects,
    surveys: data.surveys.map(survey => survey.name)
    // toastrs: [newToastr, ...state.toastrs]
  };

}

I did attempt to update the toastr's slice of the store in the ObjectSearch reducer after connecting in the ObjectSearch Component:

reducers/index.js:

 const rootReducer = combineReducers({
    toastr: toastrReducer,
    ObjectSearch 
 });

components/object-search.js

@connect(store => {
  return {
     fetching: store.ObjectSearch.fetching,
     fetched: store.ObjectSearch.fetched,
     ready: store.ObjectSearch.ready,
     surveys: store.ObjectSearch.surveys,
     toastrs: store.toastr.toastrs
   };
 })

however, adding the following to reducers/object-search.js: seems to update ObjectSearch.toastrs rather than toastr.toastrs :(

  const toastrs = [...state.toastr];
  const newToastr = {
     id: guid(),
     type: 'light',
     title: 'OBJECT SEARCH ERROR',
     message: 'No Data. Please contact support.',
     options: {
       ...REDUX_TOASTR_OPTIONS,
       icon: icons.ERROR_ICON,
       status: 'error'
     }
   };
  toastrs.push(newToastr);

I'm a react/redux newbie, so any help on app structure here would be appreciated!

like image 542
Liz Avatar asked Jan 24 '17 09:01

Liz


People also ask

How do you handle errors in 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.

Can I dispatch action inside reducer?

Dispatching an action within a reducer is an anti-pattern. Your reducer should be without side effects, simply digesting the action payload and returning a new state object. Adding listeners and dispatching actions within the reducer can lead to chained actions and other side effects.

Does Redux call all reducers?

One frequently asked question is whether Redux "calls all reducers" when dispatching an action. Since there really is only one root reducer function, the default answer is "no, it does not".


1 Answers

A general pattern would be to return a state indicating that an error occurred. For instance, in the context of a TODO APP if the received action is incorrect then you can have something like :

function reducer (state = { todos: [], error: null }, action) {

   switch (action.type) {

       case "ADD_TODO":
           if (typeof action.todo === "string") {

                return Object.assign({}, state, {
                    error: {
                        code: "INVALID TODO ACTION",
                        message: "The todo action received was empty, need a .text property",
                        action
                    }
                 });

           } else {

               return { todos: [action.text, ...state.todos], error: null }

           }

   }

}

Then whatever subscriber listening to the store, upon seeing the new state returned can take action.

I think what's important is that everything is very declarative, in the reducer logic you declare the behavior of the state relative to the received action. There is no relation to the outside world / context (the function is said to be pure). Throwing an error, or calling something outside breaks the pattern.

What's interesting in this is that you can have a middleware watching for states with non-null error key and take action on this.

Another thing you may be preoccupied with is when there is a genuine error in your actions : the shape of the object is unexpected, or you have a string instead of an error etc... In this case you may have an unexpected error thrown. What will happen ?

You can see in the source that redux does not do anything. It will simply reset into its notDispatching state. So if you did not mutate state everything is still ok (as much as it can be anyways). If you did then you have an inconstitent state on your hands...

like image 97
adz5A Avatar answered Nov 15 '22 21:11

adz5A