Skip to content

Instantly share code, notes, and snippets.

@wvengen
Last active October 10, 2016 08:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wvengen/39b561607691e33bc0db050264ebc1e7 to your computer and use it in GitHub Desktop.
Save wvengen/39b561607691e33bc0db050264ebc1e7 to your computer and use it in GitHub Desktop.
Using redux-api with batch requests

redux-api provides an easy and flexible way to interface with REST APIs in Redux (e.g. when building a React app). It works pretty well when there you know beforehand what API calls you're going to make, and define them in its configuration object.

But when you want to make multiple calls to the same endpoint, and don't know beforehand how many, you're a bit out of luck. In my case, I had a product listing where each product had a category and a score. In a summary overview, I wanted to show a histogram of the scores for each category. That means: first a request to the categories endpoint, and then for each of the categories, a request to the histogram endpoint.

This is an example of how to do that.

screenshot

// example container component using rest-multi store
import React from 'react';
import rest from './rest';
import rest_multi from './rest_multi';
import Histogram from './histogram'; // histogram component not included
class CategoriesView extends React.Component {
componentDidMount() {
// first get categories
this.props.dispatch(rest.actions.productCategories.sync()).then(() => {
// then fetch score histograms for each category
const categoryIds = this.props.productCategories.data.map(cat => cat.id);
this.props.dispatch(rest_multi.actions.productHistograms({groupParam: 'category_id', groupValues: categoryIds}));
});
}
render() {
return (
<ul>
{(this.props.productCategories.data || []).map(cat => (
<li>
<strong>{cat.name}:</strong>
<Histogram data={this.props.productHistograms[cat.id]} />
</li>
))}
</ul>
);
}
}
export default connect(
({productCategories, productHistograms}) => ({productCategories, productHistograms})
)(CategoriesView);
import rest from './rest';
/*
* Store similar to redux-api that handles multiple requests.
*
* redux-api is fine when you want to request a single resource. But when you'd like
* to fetch a number of similar resources, e.g. all products a user just selected, with
* each their own api call, it isn't sufficient on its own.
*
* This store performs redux-api requests on multiple resources at once, and puts them
* under a single entry in the store.
*
* It's not a fully general solution like redux-api, but it may be come in the future, perhaps.
*
*/
function productHistograms({groupParam, groupValues, ...params}) {
return (dispatch, getState) => {
dispatch({type: 'REST_MULTI_RESET', key: 'productHistograms', count: groupValues.length});
groupValues.forEach(value => {
const requestParams = {...params, [groupParam]: value};
rest.actions.productHistogram.request(requestParams).then(data => {
const histogramData = ((data || {}).products_histogram || {}).data;
dispatch({type: 'REST_MULTI_UPDATE', key: 'productHistograms', groupValue: value, data: histogramData});
});
});
};
}
export const actions = {productHistograms};
const initialState = {
sync: false, // `true` when all requests have finished succesfully
count: null, // used internally, don't depend too much on this number of requests to make
data: {}, // keys are `groupValues`, values the api result
};
function makeReducer(name) {
return function(state = initialState, action) {
if (action.type === 'REST_MULTI_RESET' && action.key === name) {
return {...initialState, count: action.count};
} else if (action.type === 'REST_MULTI_UPDATE' && action.key === name) {
const newData = {...state.data, [action.groupValue]: action.data};
const sync = Object.keys(newData).length >= state.count;
return {...state, sync, data: newData};
} else {
return state;
}
};
}
export const reducers = {
productHistograms: makeReducer('productHistograms'),
};
const RestMulti = {actions, reducers};
export default RestMulti;
// a basic redux-api rest store
import reduxApi from 'redux-api';
import adapterFetch from "redux-api/lib/adapters/fetch";
export default ReduxApi({
productCategories: {
url: '/api/v1/products/categories',
transformer: data => (data || {}).categories,
},
productHistogram: {
url: '/api/v1/products/histogram',
transformer: data => (data || {}).products_histogram,
},
}).use('fetch', adapterFetch(fetch));
import {applyMiddleware, combineReducers, createStore} from 'redux';
import thunk from 'redux-thunk';
import rest from './rest';
import rest_multi from './rest_multi';
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers({...rest.reducers, ...rest_multi.reducers});
const store = createStoreWithMiddleware(reducer);
export default store;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment