beavy/jsbeavy/middleware/api.jsx
// we import it here to make sure it runs!
import entites from 'reducers/entities' //eslint-disable-line
import format_jsonapi_result from './format_jsonapi_result'
// Fetches an API response and normalizes the result JSON according to schema.
// This makes every API response have the same shape, regardless of how nested it was.
function callApi (endpoint, key, params) {
return fetch(endpoint, Object.assign({
credentials: 'same-origin', // keep the cookies for the session!
headers: { // we always ask for json
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}, params || {}))
.then(response =>
response.json().then(json => ({ json, response }))
).then(({ json, response }) => {
if (!response.ok) {
return Promise.reject(json)
}
return Object.assign({}, format_jsonapi_result(json, key))
})
}
// Action key that carries API call info interpreted by this Redux middleware.
export const CALL_API = 'Call API'
// A Redux middleware that interprets actions with CALL_API info specified.
// Performs the call and promises when such actions are dispatched.
export default store => next => action => {
const callAPI = action[CALL_API]
if (typeof callAPI === 'undefined') {
return next(action)
}
let { endpoint } = callAPI
const { key, types, params } = callAPI
if (typeof endpoint === 'function') {
endpoint = endpoint(store.getState())
}
if (typeof endpoint !== 'string') {
throw new Error('Specify a string endpoint URL.')
}
if (!key) {
throw new Error('Specify a result key.')
}
if (!Array.isArray(types) || types.length !== 3) {
throw new Error('Expected an array of three action types.')
}
if (!types.every(type => typeof type === 'string')) {
throw new Error('Expected action types to be strings.')
}
function actionWith (data) {
const finalAction = Object.assign({}, action, data)
delete finalAction[CALL_API]
return finalAction
}
const [requestType, successType, failureType] = types
next(actionWith({ type: requestType }))
return callApi(endpoint, key, params).then(
response => next(actionWith({
response,
type: successType
})),
error => next(actionWith({
type: failureType,
error: error.message || 'Something bad happened'
}))
)
}