This repository has been archived by the owner on Sep 21, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
makeFetchAction
Tung Vu edited this page Dec 3, 2016
·
13 revisions
This function will generate actionCreator
s and selector
s for you. Note that no reducer is created in the progress.
makeFetchAction = (
apiName: string,
endpointFactory: (args: any) => APICallingActionDescription,
) => FetchActionOutput
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. |
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
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
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 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));