redux-dataloader

Loads async data for Redux apps focusing on preventing duplicated requests and dealing with async dependencies.

MIT License

Downloads
31
Stars
138
Committers
6

Redux Data Loader

Loads async data for Redux apps focusing on preventing duplicated requests and dealing with async dependencies.

Deeply inspired by alt Data Souces API, also inspired by redux-saga.

Instead of using redux-thunk, it handles wrapped actions and sideload async data. It also caches data requests for a while in order to prevent duplicated requests.

Installation

npm install redux-dataloader --save

Usage

1. Define actions and update the request action with load()

userActions.js

import { load } from 'redux-dataloader';

export const FETCH_USER_REQUEST = 'myapp/user/FETCH_USER/REQUEST';
export const FETCH_USER_SUCCESS = 'myapp/user/FETCH_USER/SUCCESS';
export const FETCH_USER_FAILURE = 'myapp/user/FETCH_USER/FAILURE';

export function fetchUserRequest(username) {
  // use `load` to wrap a request action, load() returns a Promise
  return load({
    type: FETCH_USER_REQUEST,
    payload: {
      username: username,
    },
  })
}

export function fetchUserSuccess(username, data) {
  // ...
}

export function fetchUserFailure(username, error) {
  // ...
}

2. Create a data loader

dataloaders.js

import { createLoader, fixedWait } from 'redux-dataloader';

import * as userActions from './userActions';

const userLoader = createLoader(userActions.FETCH_USER_REQUEST, {
  /*
   * (required) Handle fetched data, return a success action
   */
  success: (context, result) => {
    // you can get original request action from context
    const action = context.action;
    const username = action.payload.username;
    return userActions.fetchUserSuccess(username, result);
  },
  /*
   * (required) Handle error, return a failure action
   */
  error: (context, error) => {
    const action = context.action;
    const username = action.payload.username;
    return userActions.fetchUserFailure(username, error);
  },
  /*
   * (optional) By default, original request action will be dispatched. But you can still modify this process.
   */
  // loading: ({ action }) => {}
  /*
   * (required) Fetch data.
   * We use yahoo/fetchr as an example.
   */
  fetch: (context) => {
    const action = context.action;
    const username = action.payload.username;

    const fetchr = context.fetchr;
    return fetchr.read('userService')
      .params({
        username,
      }).end();
  },
  /*
   * (optional) !!! Different from alt API.
   * When shouldFetch returns false, it will prevent fetching data.
   */
  shouldFetch: (context) => {
    const action = context.action;
    const username = action.payload.username;
    const getState = context.getState;
    return !getState().user.users[username];
  }
}, {
  ttl: 10000,
  retryTimes: 3,
  retryWait: fixedWait(500),
});

export default [userLoader];

3. Register middleware

configureStore.js

import { createStore, applyMiddleware } from 'redux';
import { createDataLoaderMiddleware } from `redux-dataloader`;
import { Fetchr } from 'fetchr';
import reducer from './reducers';
import loaders from './dataloaders';

const fetcher = new Fetcher({
  xhrPath: '/api',
});

// create middleware, you can add extra arguments to data loader context
const dataLoaderMiddleware = createDataLoaderMiddleware(loaders, { fetchr });

const store = createStore(
  reducer,
  applyMiddleware(dataLoaderMiddleware)
)

// ...

4. Use it for your application

Then, just use it in your application. The following is an example that combined with redial for isomorphic use.

import { provideHooks } from 'redial';
import { fetchUserRequest } from 'userActions';
import { fetchArticleRequest } from 'articleAction';
import { fetchArticleSkinRequest } from 'articleSkinAction';

// the router location is: /:username/:articleId
// Data dependency: user <= article <= articleSkin
async function fetchData({param, dispatch, getState}) {
  try {
    // 1. Fetch user
    const username = params.username;
    await dispatch(fetchUserRequest(username)); // wait for response

    // 2. Fetch article by userId and articleId, you may use useId for authentication
    const user = getState().user.users[username];
    if (!user) {
      throw new Error(`user_not_found: ${username}`);
    }
    const articleId = params.articleId;
    await dispatch(fetchArticleRequest(user.id, articleId));

    // 3. Fetch article skin by articleId
    const article = getState().article.articles[articleId];
    if (!article) {
      throw new Error(`article_not_found: ${articleId}`);
    }
    await dispatch(fetchArticleSkinRequest(article.skinId));
  } catch (err) {
    // ...
  }
}

function mapStateToProps(state, owndProps) {
  // ...
}

@connect(mapStateToProps)
@provideHooks({
  fetch: fetchData,
})
export default class ArticleContainer extends React.Component {
  // ...
}

You can also write fetchData() with Promise:

function fetchData({param, dispatch, getState}) {
  return Promise.resolve().then(() => {
    // 1. Fetch user
    const username = params.username;
    return dispatch(fetchUserRequest(username));
  }).then(() => {
    // 2. Fetch article by userId and articleId, you may use useId for authentication
    const user = getState().user.users[username];
    if (!user) {
      throw new Error(`user_not_found: ${username}`);
    }
    const articleId = params.articleId;
    return dispatch(fetchArticleRequest(user.id, articleId));
  }).then(() => {
    // 3. Fetch article skin by articleId
    const article = getState().article.articles[articleId];
    if (!article) {
      throw new Error(`article_not_found: ${articleId}`);
    }
    return dispatch(fetchArticleSkinRequest(article.skinId));
  }).catch((err) => {
    // error handler
    // ...
  })
}

Documentation

License

MIT

Package Rankings
Top 9.84% on Npmjs.org
Badges
Extracted from project README
CircleCI npm