Skip to content
This repository was archived by the owner on Sep 21, 2024. It is now read-only.

Custom Adapter

Tung Vu edited this page Jun 20, 2017 · 9 revisions

Custom Adapter is a simple yet powerful method to add your own logic to the request/response flow with redux-api-call

Concepts

Beside the default middleware, redux-api-call package also exports 2 important functions:

  1. createAPIMiddleware(adapter: APIAdapter): ReduxMiddleware
  2. composeAdapters(...transformers: Transformer[]): APIAdapter

createAPIMiddleware

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-promise

If 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 });
};

composeAdapters

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 does

composeAdapters 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);
}

Clone this wiki locally