agraboso / agraboso/redux-api-middleware

[Recipe] API error handling

未關閉
#74 11 則留言 10 個 reaction 已指派 0 人 在 GitHub 檢視
docs
主要語言
JavaScript
星號
1.5k
分支
190
PR 合併指標
30 天內沒有已合併 PR

描述

I was struggling to come up with an effective way to leverage the failure FSAs that are dispatched by this package to handle API errors (mainly 401 and 403 responses) in a DRY manner. I have come up with this approach and thought it might be useful for some others and, if possible, for comment from the package contributors if I'm missing something...
### General concepts:
- A new middleware to handle ApiErrors
- The Failure FSA needs to be customised to include some information in the `meta`
- A function which acts as a handler and passes to the appropriate action
- An action to handle the failure

Here's an example:

_Middleware:_

``` javascript
// apiError.js
import {logout} from '../modules/auth'

export default store => next => action => {

// Checks to see if the action has a payload and if the payload is an ApiError

if (action.payload && action.payload.constructor.name === 'ApiError') {
if (action.payload.status === 403) {
if (action.error && action.meta) {
store.dispatch(action.meta.handler(action.meta.pushTo, action.meta.errorMsg))
} else {
// To avoid getting stuck in middleware.
return next(action)
}
} else if (action.payload.status === 401) {
var errorMsg = 'Your session has expired. Please login again.'
store.dispatch(logout(true, errorMsg))
}
} else {
// So the middleware doesn't get applied to every single action
return next(action)
}
}
```

_Redux Module (as per react-redux-starter-kit) which has actions, constants, and reducer:_

``` javascript
// contacts.js - my redux module
...

//Action to call update with extended description
export function updateContact(selfLink, params) {
return {
[CALL_API]: {
method: 'put',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('id_token')
},
endpoint: BASE_API_URL + selfLink,
types: [
UPDATE_CONTACT_REQUEST,
UPDATE_CONTACT_SUCCESS,
{
type: UPDATE_CONTACT_FAILURE,
meta: {
handler: handleContactError,
pushTo: '/contacts',
errorMsg: 'Sorry, you\'re not able to update this contact'
}
}
],
body: params
}
}
}

/**
* This function is the handler for any contact error. It will push to the provided route (pushTo) and then dispatch an
* action with the error message to display. The reducer consequently will set the isError state to True which can be
* used to display in the view.
* @param pushTo {String} - a string which represents a route - e.g. '/' or '/contacts'. Defaults to '/'
* @param errorMsg - a string which can be displayed to the end-user
* @returns {function()}
*/
export function handleContactError(pushTo = '/', errorMsg = null) {
return dispatch => {
dispatch(push(pushTo))
dispatch(contactError(errorMsg))
}
}

/**
* This action is called when both the isError flag and errorMsg params are present
* @param errorMsg {String} - The error message which has caused the logout
*/
function contactError(errorMsg) {
return {
type: CONTACT_ERROR,
payload: {
errorMsg: errorMsg
}
}
}

...

// IN THE REDUCER
case CONTACT_ERROR:
return Object.assign({}, state, {
isFetching: false,
isError: true,
errorMsg: action.payload.errorMsg
})

```

The general idea being that if you extend to have more modules - that the same concepts as above can be done and the middleware will handle it if there is a failure. This is also true of other actions in the same module. For example, I can also extend the failure for the `fetchContact(id)` action and if it fails due to a 403 then this will be handled by the middleware as well.

I hope this is useful / the appropriate place to put this. Would love to hear your thoughts on how this could be improved further.

貢獻指南

開啟貢獻指南

評估

這個 Issue 還沒有評估資料。

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。