Arc
Arc is a dependency free, 2kb lib to handle async requests in redux.
[
Why
Many applications are built with react and redux. Api calls are critical to this process. With the available alternatives (sagas, observables, etc...), you end up writing and repeating to much code.
With a declarative way, you can write less code and make it easier to understand and maintain. All of it leads you to have less bugs and have a better code base. This is powerful and flexible. We make things easier for you for the most common cases, and we allow you to take full control when you need!
Oh, I forgot to mention, this is 100% covered by tests!
Say no more to having a bunch of files just to talk with your api.
with Arc, you can turn a lot of files in a few lines, take a look:
import { createAsyncActions } from 'redux-arc';
const { creators, types } = createAsyncActions(
{
list: { url: 'path/to/resource', method: 'get' },
read: { url: 'path/to/resource/:id', method: 'get' },
create: { url: 'path/to/resource', method: 'post' },
},
{ prefix: 'MY_RESOURCE_'}
);
dispatch(creators.list());
dispatch(creators.read({ id: '123' }));
dispatch(creators.create({ payload: { name: 'John Doe' } }));
types.LIST.REQUEST
types.LIST.RESPONSE
Getting started
yarn add redux-arc
or
npm i --save redux-arc
In your store config file, you must configure the middleware:
import { createAsyncMiddleware } from 'redux-arc';
const asyncTask = store => done => (options) => {
done(err, response);
return response;
};
const asyncMiddleware = createAsyncMiddleware(asyncTask);
const store = createStore(
reducer,
applyMiddleware(asyncMiddleware),
);
We let the request with you. This way, you can use whatever you want: promises, generators, etc...
And now, you can use createAsyncActions
to define your action creators and types.
import { createAsyncActions } from 'redux-arc';
const { creators, types } = createAsyncActions(
{
list: { url: 'path/to/resource', method: 'get' },
read: { url: 'path/to/resource/:id', method: 'get' },
create: { url: 'path/to/resource', method: 'post' },
update: { url: 'path/to/resource/:id', method: 'put' },
},
{ prefix: 'MY_RESOURCE_'}
);
The action creators name is up to you! We only care about the config inside it. You can create a softDelete
, for example.
Action Creators
creators
is just an object that contains the action creators you defined (list
, read
, create
, update
).
you can execute any of them passing an object, which can contain a payload
, used in the request, and any other value, that will be used to parse the url params that you declare, and will be forwarded to asyncTask
dispatch(creators.read({ id: '123'}));
The above code, would dispatch an action like this:
{
type: ['MY_RESOURCE_LIST_REQUEST', 'MY_RESOURCE_LIST_RESPONSE'],
meta: {
url: 'path/to/resource/123',
method: 'get',
id: '123',
},
}
If you don't like using asyncActionCreator, you can always create and dispatch an object as the above, it will work out of the box.
Our middleware will intercept that action, and will dispatch actions
MY_RESOURCE_LIST_REQUEST
and MY_RESOURCE_LIST_RESPONSE
in the respective time.
To perform the request, the middleware uses the function you provide (asyncTask
). It will execute the asyncTask passing store => done => options
,
- store: if you like, you can access the state through the
store.getState()
. - done: the function you must call with the
err
and response
: done(err, response)
when the request finishes. - options: the object containing information regarding to the request, as the example bellow:
{
url: 'path/to/resource/123',
method: 'get',
payload: {},
}
If you provide extra params in the actionCreator call, all of them will be under the options object.
Types
When you call createAsyncActions
, you also receive types. Types is just an object that contains all your action types, including request and response.
Basically, what we do, is converting to uppercase the name you gave for your action creators. So, if you provide a name like list
, you will have
types.LIST
, which is and object containing REQUEST
and RESPONSE
so, in your reducers, you could use types.LIST.REQUEST
to check for an action type. The respective type, has a value like this:
'MY_RESOURCE_LIST_REQUEST'
. Remember we provided a prefix option (MY_RESOURCE_
)? This helps you avoid conflicts across the application.
Polices
We know there are sometimes when you need perform operations changing a request or response. For those cases, you can use polices.
A police is basically another middleware, as the follow example:
const police store => done => (action, error, response) =>
done(action, error, response);
A police must have an applyPoint attribute, so:
police.applyPoint = 'beforeRequest'
You can imagine, in the cases your police has an applyPoint 'beforeRequest'
, you would only have access to action
object, unless another police create and error
or response
in the ``beforeRequest` chain.
To use a police, you do as the follow:
import { createAsyncActions, polices } from 'redux-arc';
const { creators, types } = createAsyncActions(
{
update: {
url: 'path/to/resource/:id',
method: 'put',
polices: ['omitId'],
},
},
{ prefix: 'MY_RESOURCE_'},
);
function omitId(options) {
return store => done => (action, ...params) => {
const { id, ...restAction } = action;
return done(restAction, ...params);
}
}
omitId.applyPoint = 'beforeRequest';
polices.register('omitId', omitId);
Usually, for beforeRequest
you would change only the action value, and for onResponse
, you would change only the response. But feel free to change the action inside onResponse
cycle if that makes sense.
The best way to use a police, is defining it in the action creators config, as we saw above, but if you need, you can provide polices when you are calling the action creator, just like this:
dispatch(creators.read({
id: '123',
polices: ['omitId'],
}));
License
MIT