Created
June 17, 2018 12:00
-
-
Save brigand/9816f97873b21dfa0548a96eeb090434 to your computer and use it in GitHub Desktop.
Custom redux api middleware. Take and modify to fit your needs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Example action: | |
{ | |
type: 'MY_API', | |
payload: { | |
method: 'POST', | |
path: 'foo/:userId', | |
params: { userId: '123' }, | |
query: { my_query: 1 }, | |
body: { my_body: 2 }, | |
actions: ['FETCH_FOO', 'FETCH_FOO_SUCCESS', 'FETCH_FOO_FAIL'] | |
}, | |
} | |
*/ | |
export type Request<Q, B> = { | |
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH', | |
path: string, | |
query?: Q, | |
params?: { [paramName: string]: string | number }, | |
actions: [string, string, string], | |
body?: B, | |
map_success?: (res_body: any, res: any) => any, | |
map_error?: (res_body: any, res: any) => any, | |
meta?: any, | |
}; | |
export type ApiAction<Q, B> = { | |
type: 'MY_API', | |
payload: Request<Q, B>, | |
}; | |
export function apiAction<Q, B>( | |
request: Request<Q, B>, | |
meta: any, | |
): ApiAction<Q, B> { | |
return { | |
type: 'MY_API', | |
payload: request, | |
meta, | |
}; | |
} | |
function debugPrintObject(obj: ?Object) { | |
if (!obj) return String(obj); | |
const keys = Object.keys(obj); | |
if (keys.length < 5) return keys.join(','); | |
return `${keys.slice(0, 4).join(',')}…`; | |
} | |
function debugPrintRequest(request: Request<any, any>) { | |
return `{path=${String(request.path)}, query={${debugPrintObject( | |
request.query, | |
)}}, body={${debugPrintObject(request.body)}}`; | |
} | |
async function performRequest<Q, B>(request: Request<Q, B>) { | |
let url = request.path; | |
if (url.startsWith('/')) | |
throw new Error( | |
`'path' can't start with /, for request ${debugPrintRequest(request)}`, | |
); | |
if (request.params) { | |
url = url.replace(/(^|\/):(\w+)(\/|$)/g, (m, pre, ident, post) => { | |
// $FlowFixMe | |
const param = request.params[ident]; | |
if (param == null) { | |
throw new Error( | |
`Attempted to replace segment :${ident} but request.params didn't contain it.`, | |
); | |
} | |
return `${pre}${encodeURIComponent(String(param))}${post}`; | |
}); | |
} | |
url = `/api/v1/${url}`; | |
if (request.query) { | |
url = `${url}?${qs.stringify(request.query)}`; | |
} | |
const fetchParams: any = { | |
method: request.method, | |
headers: { | |
}, | |
}; | |
if (request.body) { | |
fetchParams.headers['content-type'] = 'application/json'; | |
} | |
try { | |
const res = await global.fetch(url, fetchParams); | |
let body = await res.json(); | |
if (res.ok) { | |
if (request.map_success) { | |
body = request.map_success(body, res) || body; | |
} | |
return { | |
type: request.actions[1], | |
payload: body, | |
meta: { | |
headers: res.headers, | |
req: { | |
...request.query, | |
...request.body, | |
}, | |
...request.meta, | |
}, | |
}; | |
} | |
if (request.map_error) { | |
body = request.map_error(body, res) || body; | |
} | |
return { | |
type: request.actions[2], | |
payload: body, | |
meta: { | |
headers: res.headers, | |
req: { | |
...request.query, | |
...request.body, | |
}, | |
...request.meta, | |
}, | |
}; | |
} catch (e) { | |
console.error(`Fetch failed, or internal error`, e); | |
return { | |
type: request.actions[2], | |
payload: { | |
message: e.message, | |
}, | |
meta: { | |
headers: {}, | |
req: { | |
...request.query, | |
...request.body, | |
}, | |
...request.meta, | |
}, | |
}; | |
} | |
} | |
const apiMiddleware = (store: any) => (next: any) => (action: any) => { | |
if (action.type !== 'MY_API') { | |
return next(action); | |
} | |
// Start request on next tick | |
Promise.resolve().then(async () => { | |
const resAction = await performRequest(action.payload); | |
store.dispatch({ ...resAction }); | |
}); | |
return next({ | |
type: action.payload.actions[0], | |
payload: { | |
...action.payload.query, | |
...action.payload.body, | |
}, | |
meta: { | |
req: { | |
...action.payload.query, | |
...action.payload.body, | |
}, | |
...action.payload.meta, | |
}, | |
}); | |
}; | |
export default apiMiddleware; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment