-
Notifications
You must be signed in to change notification settings - Fork 19
Custom Adapter
Custom Adapter is a simple yet powerful method to add your own logic to the request/response flow with redux-api-call
Beside the default middleware, redux-api-call package also exports 2 important functions:
createAPIMiddleware(adapter: APIAdapter): ReduxMiddlewarecomposeAdapters(...transformers: Transformer[]): APIAdapter
For simple use-cases, you only need to use createAPIMiddleware and pass an adapter to it. The simplest form of adapter is:
import { createAPIMiddleware } from 'redux-api-call';
const simplestAdapter = (getState) => async (req) => {
return {
payload: { key: 'value' },
meta: { key: 'value' }
};
};
const apiMiddleware = createAPIMiddleware(simplestAdapter);
// you now can use apiMiddleware wherever you might want to use redux-thunk or redux-promiseIf you're not familiar with async/await syntax, you can always use Promise instead.
// this above example but use Promise
const simplestAdapter = (getState) => (req) => new Promise(resolve => resolve({
payload: { key: 'value' },
meta: { key: 'value' }
}));In real world, you will need a more sophisticated adapter like this:
const jqAjaxAdapter = (getState) => (req) => new Promise((resolve, reject) => {
$.ajax({
url: req.endpoint,
method: req.method,
data: req.body,
headers: req.headers,
success: (resp, statusText, xhr) => {
resolve({ payload: resp, meta: xhr.getAllResponseHeaders() });
},
error: (resp, statusText, xhr) => {
reject({ payload: resp, meta: xhr.getAllResponseHeaders() });
}
});
});Note that you can always access to redux store using getState function passed along in the adapter.
const fetchWithTokenAdapter = (getState) => async (req) => {
const state = getState();
const token = state.session.token; // token may be stored where else in your application
const { endpoint, headers, ...others } = req;
const newHeaders = {
...headers,
authorization: `Token token=${token}`
};
return fetch(endpoint, { ...others, headers: newHeaders });
};If you want to add more custom logic to your request/response flow, it's better to decompose them to smaller chunk of code. This is where composeAdapters shines. Think of it like the redux's applyMiddlewares function.
import { composeAdapters } from 'redux-api-call';
const fetch = (next) => async ({endpoint, ...others}) => {
const resp = await fetch(endpoint, others);
if (!resp.ok) {
const error = new Error('Bad Response');
error.payload = await resp.text();
error.headers = resp.headers;
throw error;
}
return next({
payload: await resp.text(),
meta: resp.headers,
});
};
const json = (next) => async (req) => {
try {
const { payload, ...others } = await next(req);
return {
payload: tryJSON(payload),
...others
}
} catch ({ payload, ...others }) {
throw {
payload: tryJSON(payload),
...others
}
}
}
const adapter = composeAdapters(json, fetch);
// this above example is what the default middleware doescomposeAdapters may help you to implement one unit of logic (transformer or interceptor) at a time and compose them before passing the final adapter to createAPIMiddleware.
Other examples transformer:
// get token from redux store and add it to authorization header
const addToken = (next, getState) => async (req) => {
const token = getState().sessions.token;
const nextReq = {
...req,
headers: {
...req.headers,
authorization: `Token token=${token}`
}
};
return next(nextReq);
}// stringify a body object and add request headers
const stringify = (next) => async (req) => {
// only stringify POST, PUT, PATCH requests with body is an object
if (req.method.match(/POST|PUT|PATCH/) && typeof req.body === 'object') {
const body = JSON.stringify(req.body);
const headers = {
...req.headers,
'content-type': 'application/json'
}
return next({ ...req, body, headers });
}
return next(req);
}