README.md
May 24, 2017 · View on GitHub
Flux Standard Ajax Action (exploratory)
A human-friendly standard for Ajax Flux action objects. Feedback welcome.
Motivation
The motivation behind this is two-fold and was sparked by the Redux discussion about potential abstractions to ease its learning curve. Firstly, Ajax seems to be an area where new learners have difficulty, whether they're learning to use thunks, sagas or epics. One area where they don't seem to have much difficulty is creating actions - as they're just plain old Javascript objects™️, and we have the added clarity provided by the Flux Standard Actions spec. Secondly, as the Redux eco-system grows, more libraries will have the need to make requests on your behalf e.g. redux-offline. Currently there is no standard way to make requests, which forces these libraries to roll their own implementation.
This line of thinking led to the question - Why not develop a Flux Standard Ajax Action?
A basic Flux Standard Ajax Action:
function fetchPosts(userId) {
return {
type: 'FETCH_POSTS_REQUEST',
ajax: {
url: `/api/users/${userId}/posts`,
method: 'GET',
}
};
}
The ajax action would be handled by FSAA compliant middleware (which would react to the ajax key in the action).
Design Goals
- Should be easy to read and write - easing the learning curve of writing Ajax in Redux applications
- Should be flexible - common Ajax request requirements should be covered including request cancellation, debouncing, timeouts, retries
- Should enable the creation of useful tools and abstractions
- Should be an extension of FSA
- Should be idiomatic Redux (action log should still make sense, serializable etc)
Estimated Benefits
- a dramatic decrease in the amount of async actions required in your average application (as the ajax related async actions would fall away)
- it's declarative, implementation details no longer the concern of the developer
- different FSAA compliant middleware could be swopped in/out without breakages, which should allow for some competition
Details
function likePost(postId) {
return {
type: 'LIKE_POST_REQUEST',
ajax: {
url: `/api/posts/${postId}/likes`,
method: 'POST',
// Optional request data
data: { ... },
// Optional request meta attributes
meta: {
// Amount of times to retry the request on failure
retries: 2,
// Amount of time before timing out
timeout: 10000,
// The action type that cancels the request
cancelType: 'LIKE_POST_CANCELLATION',
// Amount of time to debounce the execution of the request
debounce: 400,
// Request resolving strategy when multiple LIKE_POST_REQUESTs are in flight
resolve: 'LATEST',
// Fine tune debouncing and request resolving.
// E.g. you could debounce LIKE_POST_REQUESTs by post id
group: postId,
// Broaden debouncing and request resolving across related action types.
// E.g. you could debounce LIKE_POST_REQUEST and UNLIKE_POST_REQUESTs together
// if both have the same groupUid
groupUid: `like-unlike-${postId}`,
},
// Optional request headers
headers: { ... },
// Optional response callback
response: data => normalize(...),
// Optional response type
responseType: 'json',
// Optional chain of FSAAs to dispatch sequentially
chain: [
res => fetchFoo(...),
res => fetchFooBars(...)
],
// Optional cross domain flag
crossDomain: false,
// Optional credentials flag
withCredentials: false,
// Optional username and password to be used with XMLHttpRequest
username: '...',
password: '...',
}
};
}
GET Request Data
- Should be encoded into query string params
- Should handle encoding arrays in
indices,bracketsorrepeatformat - Should allow array encoding format to be configured
Non-GET Request Data
- Should be sent as JSON
Request Headers
- Should add
'Content-Type': 'application/json; charset=UTF-8'by default if not provided
Response Callbacks
- Should be removed from the action in the middleware so that they don't make their way into the action log or the reducers
Response Types
- Should be auto generated
LIKE_POST_REQUEST -> LIKE_POST_SUCCESS | LIKE_POST_FAILURE
- Should be configurable when creating the middleware. E.g.
createAjaxMiddleware({
requestSuffix: 'REQUEST',
successSuffix: 'SUCCESS',
failureSuffix: 'FAILURE'
});
Successful/Fulfilled Requests
- Should copy over the
ajaxattribute from the request into itsmetaattribute
{
type: 'LIKE_POST_SUCCESS',
// The response data
payload: { ... },
meta: {
ajax: {
url: '/api/posts/42/likes',
method: 'POST'
}
}
}
Failed Requests
- Should follow the approach outlined in the FSA spec for errors
- Should include HTTP error information in the payload
- Should copy over the
ajaxattribute from the request into itsmetaattribute
{
type: 'LIKE_POST_FAILURE',
error: true,
payload: {
status: [status code],
text: [status text]
},
meta: {
ajax: {
url: '/api/posts/42/likes',
method: 'POST'
}
}
}
Request Chains
- Should be removed from the action in the middleware so that they don't make their way into the action log or the reducers
General thoughts on chaining requests
- Should only be used for unique data flows (edge cases)
- If you find yourself using these chains often, then it could be time to look into redux-observable / redux-saga for setting up these side effects
FSAA Compliant Middleware
redux-ajaxable built with RxJS
Prior Art
redux-api-middleware also introduced the concept of having a standard action, which they called Redux Standard API-calling Action. There are some important differences between it and FSAA including:
- it doesn't have the Ajax meta attributes dealing with request cancellation, retries, resolve strategies etc
- the action isn't an extension to FSA, so doesn't naturally fit in with your other FSA action creators
- all three types (request, success, failure) need to be provided for each request
The project's future also seems in doubt as activity has died down and new maintainers are required.