redux-berry v1.0.1
Installation
yarn add redux-berry
Usage
In store configuration file
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducer';
import berry from 'redux-berry';
export default createStore(reducer, applyMiddleware(berry));
Apply redux-berry as the first middleware, if you are going to use other middlewares together. Otherwise, it may not work as intended.
In your actions.js
files
import axios from 'axios';
export const GET_USER = 'GET_USER';
export const getUser = ({ id }) => ({
type: GET_USER,
payload: axios.get(`/user/${id}`)
});
In your reducer.js
files
import { GET_USER } from './actions';
const initialState = {
user: {}
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_USER:
return Object.assign({}, state, {
user: action.payload.data.user
});
default:
return state;
}
};
Features
You can organize your actions in a consistent way.
If an action should be dispatched as a result of an asynchronous task, simply declare the task in the action's payload
property as a Promise
.
The action will be passed to reducers after the Promise
is resolved, and the payload
of action will be replaced with the resolved value of the promise so you can handle it in your reducers.
You can use some helpful functions for asyncronous task
By defining the meta
property of actions, you can apply some features in your asyncronous tasks. Please go see BerryMeta
.
throttle
type | default |
---|---|
number | 0 |
When throttle
property is set, the action will be ignored if there are same type actions dispatched in the last throttle
ms.
It will be ignored, if the action is dispatched in the before, after, error array.
debounce
type | default |
---|---|
number | 0 |
When debounce
property is set, the action will be handled only if there are no same type actions dispatched for the next debounce
ms.
It will be ignored, if the action is dispatched in the before, after, error array.
lock
type | default |
---|---|
boolean | false |
When lock
property is set to true
, all dispatch of same type action is blocked until the action is resolved.
before
type | default |
---|---|
Array<AnyAction | Function> | [] (empty array) |
All actions and callbacks will be handled serially according to their type before execute Promise
.
- Action - They are simply dispatched
- Function - They are executed, and if there is a return, it will be disaptched, so they should not return other than action. It is useful when it is needed to determine whether to dispatch sub actions or not according to arguments of the action creator.
Dispatch of asyncronous actions in before, after, error is not parallel. This means that a dispatch of an action in these array put off handling next item (no matter it is action or function) until the action is settled.
const getUserInfoAsync = ({ id }) => {
type: ASYNC_ACTION,
payload: axios.get(`/user/${ id }`).then(res => res.data),
meta: {
before: [
{ type: 'setLoadingSpinner', payload: true }
]
}
};
after
type | default |
---|---|
Array<AnyAction | Function> | [] (empty array) |
All actions and callbacks will be handled serially according to their type after Promise
is settled. Once the Promise
get settled, They are handled regardless of whether the Promise
has been resolved or rejected.
- Action - same with before
- Function - same with before except it receives resolved value as a first argument and rejected value as a second argument.
const getUserInfo = ({ id }) => {
type: GET_USER_INFO,
payload: axios.get(`/user/${ id }`).then(res => res.data),
meta: {
before: [setLoadingSpinner(true)],
after: [
data => getTrendyArticles(data.favoriteCategory),
setLoadingSpinner(false) // It will be dispatched after the above promise(getTrendyArticles) is settled
]
}
};
const getTrendyArticles = payload => {
type: GET_TRENDY_ARTICLES,
payload: axios.get(`/articles/${payload}/trendy`).then(res => res.data)
};
const setLoadingSpinner = payload => ({
type: SET_LOADING_SPINNER,
payload
});
error
type | default |
---|---|
Array<AnyAction | Function> | [] (empty array) |
All actions and callbacks will be handled serially according to their type only when Promise
is rejected.
- Action - same with before
- Function - same with before except it receives rejected value as first argument.
import { commonErrorHandler, userErrorHandler } from './errHandler';
const getUserInfo = ({ id }) => {
type: GET_USER_INFO,
payload: axios.get(`/user/${ id }`).then(res => res.data),
meta: {
error: [commonErrorHandler, userErrorHandler]
}
};
with Typescript
Redux-berry supports typescript. If you want to use redux-berry and typescript together, just import BerryActionCreator
and specify it as the type of your asyncronous action creator.
import { BerryActionCreator } from 'redux-berry';
const GET_USER_INFO = 'GET_USER_INFO';
const getUserInfo: BerryActionCreator<
{ id: string; }, // payload type
{ data: { user: any; }; }, // resolved data type
{ error: any; } // rejected data type
> = ({ id }) => { // type inference for "id" works
type: GET_USER_INFO,
payload: axios.get(`/user/${ id }`),
meta: {
after: [
(res, err) => { ... } // type inference for "res" and "err" works
],
error: [
(err) => { ... } // type inference for "err" works
]
}
};
Types exported from redux-berry
type BerryActionCreator
/**
* An action creator type which returns BerryAction
*
* @template TPayload the type of argument of action creator
* @template TResolve the type of resolved value from the promise of the action
* @template TReject the type of rejected value from the promise of the action
* @template TType the type of promise of action returned from the action creator
*/
export type BerryActionCreator<TPayload = any, TResolve = any, TReject = any, TType = string> = {
(payload?: TPayload): BerryAction<TResolve, TReject, TType>;
};
type BerryAction
/**
* An action type which is handled by redux-berry
*
* @template TResolve the type of resolved value from the promise
* @template TReject the type of rejected value from the promise
* @template TType the type of action's "type" property
*/
type BerryAction<TResolve = any, TReject = any, TType = string> = {
type: TType;
payload: Promise<TResolve>;
meta?: BerryMeta<TResolve, TReject>;
};
type BerryMeta
/**
* A meta type of BerryAction
*
* @template TResolve the type of resolved value from the promise of the action
* @template TReject the type of rejected value from the promise of the action
*/
type BerryMeta<TResolve = any, TReject = any> = {
throttle?: number;
debounce?: number;
lock?: boolean;
before?: (AnyAction | ((...args: any[]) => void | AnyAction))[];
after?: (AnyAction | ((res?: TResolve, err?: TReject, ...args: any[]) => void | AnyAction))[];
error?: (AnyAction | ((err?: TReject, ...args: any[]) => void | AnyAction))[];
};