Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a global error handler in Redux and override it when needed?

Imagine a situation of handling form submit which can return different errors: 400, 401, 500. When 400 is returned, I want to show a message in top of the form (override the default behavior). For other (unhandled) error codes, the default (global) error handler should be invoked (which shows notification toast). Just don't want to duplicate this code for every single action

I dispatch async actions using redux-thunk middleware

// Pseudo code
const action = (dispatch) => {

    const onSuccess = (result) => dispatch({type: 'OPERATION_SUCCESS', payload: result});
    const onError = (error) => dispatch({type: 'OPERATION_ERROR', error: true, payload: error});

    return promise.then(onSuccess, onError);

};
dispatch(action);

I can create a reducer which handles all {error: true} actions and show some popup-notification (probably without using redux state, directly invoking some toast.show() method) But how to determine if this special error was already handled by some other reducer?

like image 712
Konstantin Smolyakov Avatar asked Feb 15 '16 19:02

Konstantin Smolyakov


1 Answers

By the time an action reaches a reducer, it is a fact. It reflects something that has already happened. There is no sense in asking “has other reducer handled this action?” because reducers are supposed to be passive and, in general sense, unaware of each other’s existence. They should strive to be independent, where possible.

There is no one “true” way to accomplish what you wanted, but since you already use the convention of treating any object with an error property as a global error, you might as well introduce another convention like “if the action has a suppressGlobalErrorNotification flag then the global error reducer should not care about it”.

// utilities

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response
  } else {
    const error = new Error(response.statusText)
    error.response = response
    throw error
  }
}

function parseJSON(response) {
  return response.json()
}

export function post(url, data) {
  const options = {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)  
  }
  return fetch(url, options)
    .then(checkStatus)
    .then(parseJSON)
}

// action creators

import { post } from './utils'

export function submitForm(data) {
  return dispatch => post('/myform', data).then(
    response => dispatch({
      type: 'SUBMIT_FORM_SUCCESS',
      payload: response
    }),
    error => dispatch({
      type: 'SUBMIT_FORM_FAILURE',
      error: error,
      suppressGlobalErrorNotification: (
        error.response &&
        error.response.status === 400
      )
    })
  )
}

// reducers

export function error(state = null, action) {
  if (!action.error || action.suppressGlobalErrorNotification) {
    return state
  }
  if (action.type === 'RESET_ERROR') {
    return null
  }
  return action.error
}


export function form(state = {}, action) {
  switch (action.type) {
  case 'SUBMIT_FORM_FAILURE':
    return Object.assign({}, state, { isFormError: true })
  // ...
  default:
    return state
  }
}
like image 113
Dan Abramov Avatar answered Oct 11 '22 11:10

Dan Abramov