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

makeFetchAction

Tung Vu edited this page Dec 3, 2016 · 13 revisions

makeFetchAction()

This function will generate actionCreators and selectors for you. Note that no reducer is created in the progress.

Signature

makeFetchAction = (
  apiName: string,
  endpointFactory: (args: any) => APICallingActionDescription,
) => FetchActionOutput

Params

name type required description
apiName string yes act as an identifier for each API. It is the key in your redux store under api_calls
endpointFactory Function yes A function that returns this API description, think of it as a request template that is called on every outgoing request for this particular API.

API Calling Action Description

A plain object that describes your request. It includes (but not limits to) the following keys:

key type required description
endpoint string yes your api endpoint address. It can be absolute or relative to your current location. To handle querystring, your might want to use npm package query-string or modern browser API URLSearchParams.
method string yes HTTP verb, either "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"
credentials string no define how cookie data is attached in the request. Either: "same-origin", "omit", or "include"
headers object no Any headers you want to add to your request
body string no Note that a request using the GET or HEAD method cannot have a body
mode string no The mode you want to use for the request. Either "cors", "no-cors", or "same-origin"

For detailed specifications of API Calling Action Description, please visit window.fetch document page

Dynamic description

Every value in the description can be a function. When a value is a function, it must be a "selector" (ie, a function that takes redux state) and must return the actual value. This process is done in middleware

Return object

The output of makeFetchAction() is an object that includes the following keys, where each of the values is a function

key type description
dataSelector selector a function that takes state and returns the latest fetch result or null if no success call is made.
errorSelector selector a function that takes state and returns the latest fetch error or null if no failed call is made.
isFetchingSelector selector a function that takes state and returns true if there's an outgoing request for this api.
actionCreator action creator when invoked, this function will compute an APICallingActionDescription and return a FluxStandardAction or a "thunk-like" Action (see above). When bound, this will eventually trigger a fetch call to server using middleware.
updater action creator (experimental) when invoked with a plain object, this will return an action that attempt to replace the latest fetch result with that object. Useful for optimistic update.

Example:

// EXAMPLE 1
// this will create data selector and action creator for your components to use
const { dataSelector, actionCreator } = makeFetchAction(
  'TODO_LIST',
  ({ page, limit }) => ({
    endpoint: `/api/v1/todos?page=${page}&limit=${limit}`,
    method: 'GET',
  })
);

// trigger fetch action
store.dispatch(actionCreator({ page: 1, limit: 10 });
// EXAMPLE 2:
// use data from redux store to construct APICallingAction
makeFetchAction(
  'MY_TODO_LIST',
  ({ page, limit }) => ({
    endpoint: state => `/api/v1/todos?page=${page}&limit=${limit}&userId=${state.session.userId}`,
    method: 'GET',
  })
);
// EXAMPLE 3
// dynamic method 
makeFetchAction(
  'CHANGE_STARRED_STATUS',
  () => ({
    endpoint: '/api/v1/repos/redux-api-call/star',
    method: (state) => state.starred ? 'DELETE' : 'PUT', // if currently starred, then use DELETE, otherwise use 'PUT'
  })
);
// EXAMPLE  4
// using reselect
import { createSelector } from 'reselect';

const currentUserIdSelector = (state) => state.session.userId;
const pickedGroceriesOptions = (state) => state.cart.items.filter(item => item.type === 'GROCERY');

const bodySelector = createSelector(
  currentUserIdSelector,
  pickedGroceriesOptions,
  (userId, groceries) => JSON.stringify({
    buyer: userId,
    items: groceries
  })
);

const { actionCreator } = makeFetchAction(
  'CHECKOUT',
  () => ({
    endpoint: '/api/cart/checkout',
    method: 'POST',
    headers: {
      'CONTENT-TYPE': 'application/json',
      accepted: 'application/json'
    },
    body: bodySelector // we use the above "composed" selector
  })
)
// EXAMPLE 5
// optimistic updates (experimental feature)

const { updater, dataSelector } = makeFetchAction('LIST_ALL_TODOS', () => ({
  method: 'GET',
  endpoint: '/api/todos/'
});
const { actionCreator } = makeFetchAction('MARK_TODO_AS_DONE', (id) => ({
  method: 'PUT',
  endpoint: `/api/todo/${id}`,
  body: JSON.stringify({ done: true }),
  headers: { 'content-type': 'application/json' }
}));

// create a thunk action creator
const markAsDone = (id) => (dispatch, getState) => {
  // start the actual request PUT /api/todo/{id}
  dispatch(actionCreator(id));

  // optimistic update to todos list

  // let assume allTodos is stored as a hash of id => todo
  const allTodos = dataSelector(getState());

  // we expect after submit, attribute done of the given todo must become true
  const optimisticTodo = Object.assign({}, allTodos[id], { done: true });
  const optimisticTodoList = Object.assign({}, allTodos, { [id]: optimisticTodo });

  // this will replace your latest result from API with the optimistic todo list from above.
  // if the actual request has the same result with optimistic values, nothing will change in UI.
  // otherwise, next server response will overwrite optimistic values as expected
  dispatch(updater(optimisticTodoList);
};

// usage: mark todo with id of 42 as done
store.dispatch(markAsDone(42));