Hi,
I am a huge fan of this project!
I recently wrote a blog post about an alternative async pattern to the one suggested in the redux documentation.
I would be interested to get your thoughts on the approach. If you think it is a nice pattern I could put together a PR to include it in the redux documentation.
Cheers
I've been using this pattern with good results as well.
A general middleware similar to the one found here
is very easy to implement and modify to suit a specific application's needs, I think.
Here's a snippet for those don't want to scroll through almost the entire post:
export default function promiseMiddleware() {
return next => action => {
const { promise, type, ...rest } = action;
if (!promise) return next(action);
const SUCCESS = type;
const REQUEST = type + '_REQUEST';
const FAILURE = type + '_FAILURE';
next({ ...rest, type: REQUEST });
return promise
.then(res => {
next({ ...rest, res, type: SUCCESS });
return true;
})
.catch(error => {
next({ ...rest, error, type: FAILURE });
// Another benefit is being able to log all failures here
console.log(error);
return false;
});
};
}
This practice has allowed our team to easily add more convention into the code through fixed action names, allowing developers who previously have not touched certain components to easily be able to reason about the actions and their effects. We also found that a pattern like this can easily be adapted to allow for server-side rendering, which is a big plus!
I like the pattern that @bjrnt describes above. I've used it in apps too and it's worked reliably well.
I've used it in connection with an apiMiddleware which will handle making the actual request using something like fetch or another promise library of your choosing.
I think there are many ways you could do it, one way is to put a url key on the action object to indicate the url you're looking to retrieve data from:
export function fetchInfo(url) {
return {
type: FETCH_INFO,
url
};
}
and then also include the apiMiddleware to intercept this action and place the call. Then pass along the promise to be intercepted in the promiseMiddleware above.
export default function apiMiddleware() {
return () => next => action => {
if (!action) return next(action);
const { url, type } = action;
if (!url) return next(action);
const promise = fetch(url);
return next({ promise: promise, type });
};
}
I like the suggested patterns as they remove business logic out of the action creators. However, both the examples posted are tailored for ajax / promises. The pattern I suggested as no opinions as to whether it should be used for ajax, promises or otherwise. Although you could set up a convention in similar ways to what has been suggested here - you would just do it in your listeners.
An issue with the suggested solutions is that once they do their work they call next rather than dispatch. This means that middleware higher up the chain would not know about the new action. This should be fine in most circumstances but it prevents some powerful features such as app wide action buffering. The approaches listed could call dispatch but it would be a little confusing as it would call itself again - I flesh this out in the blog.
The above approaches do not also address the concern of multiple things (I'll call them listeners) listening and responding to the same event. You would need to add extra middleware to accomplish this which has some dangers which I have listed in the blog.
Personally I find redux-saga solving a similar need but in a more composable and declarative way. This is a fine pattern though, and I鈥檓 glad that you find it useful.
+1
redux-saga is awesome, but a little complex, especially in isomorphic application.
I have created a library redux-dataloader inspired by alt DataSource API which is exactly using listener pattern.
It can load data both from local and remote data store, and prevent duplicated request by caching Promises and works very well with redial or redux-async-connect for isomorphic app.
I don't like messing with action type constants that way, because then they aren't really constants but magic strings. Instead I use a status prop with values 'success', 'failure', and 'loading'. The first action hits the middleware without a status, then the middleware dispatches the other three.
In my experience, the middleware approach has been very confusing for team members who are new to redux. The confusion, I believe, comes from the fact that middleware is not discoverable, at least not easily.
For example, in the blog post above there's a very simple action creator:
export fetchPosts = () => {
return {
type: actionTypes.FETCH_POSTS_REQUEST
};
};
but this is really an iceburg with no obvious path to discovering the complication.
I've started preferring using "decorators" around my action creators (well, functions inspired by the class/property decorators proposal).
Ie. in the API client middleware example this may look like:
// clientAction.js
// request, success, failure would set a status property on the action to be checked in the reducer
export const clientAction = action => promise => {
return (...args) => dispatch => {
dispatch(request(action));
return promise(...args)
.then(response => dispatch(success({ ...action, response })))
.catch(error => dispatch(failure({ ...action, error }));
};
}
import {clientAction} from 'clientAction';
import fetchApi from 'fetchApi'; // fetchApi is a custom wrapper around fetch for your API
export const savePost = clientAction({ type: SAVE_POST })(data => fetchApi('/posts', { method: 'POST', data });
This does a couple things differently:
The action creator here may not be _quite_ as nice looking as the listener example, but no matter my level of familiarity with a codebase I can at least trace everything that's happening here.
@joefiorini
Actually, you are still using three actions in you code (request(action), success({ ...action, response }), failure({ ...action, error })) in your code, but only one action type.
To use one action type or three, I prefer three. Because it is clear that they are different actions and contain different data structures. When using three action types, in reducer, you don't need to determine if it is a request action or a success action or a failure action, just deal with the state change.
We are using fetchr to have an isomorphic data fetching. On server side, it depends on the request from browser. We have to use middleware to inject fetchr instance to action creator or action handler like redux-dataloader.
BTW, redux-promise and redux-promise-middleware offer some codified versions of this based on Promises.
As Dan pointed out, redux-saga offers a complete solution for this kind of thing. But if you're looking to keep things simple (and don't want to involve something large-ish like regenerator in your project via babel-polyfill), this pattern and those libraries might be a good option for you.
Most helpful comment
Personally I find redux-saga solving a similar need but in a more composable and declarative way. This is a fine pattern though, and I鈥檓 glad that you find it useful.