agraboso / agraboso/redux-api-middleware
[Recipe] API error handling
- 主要語言
- 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 還沒有評估資料。