Skip to content

Instantly share code, notes, and snippets.

@dennishn
Last active November 15, 2018 12:45
Show Gist options
  • Save dennishn/f3f7468ec20e032a2ff04d86a22c1800 to your computer and use it in GitHub Desktop.
Save dennishn/f3f7468ec20e032a2ff04d86a22c1800 to your computer and use it in GitHub Desktop.
Axios HOC with Cache and configurable delay
import React, {Component} from 'react';
import axios from 'axios';
import hash from 'object-hash';
const hasAxiosClient = (baseURL, delay = 0) => WrappedComponent => {
class HasAxiosClient extends Component {
// Consumers can track the state of any request done in the hasAxiosClient HOC
// Nice for "global" loaders - alternatively all requests returns a promise so you can handle
// it individually. Think of this loading state property as an <ErrorBoundary>.
state = {
loading: false
}
axiosInstance = axios.create({
baseURL
});
// Keep track of any requests in transit so we can prevent spamming requests
requestCache = new Map();
constructor(props) {
super(props);
}
get = (path, params) =>
this._request({
url: path,
method: 'get',
params
});
remove = (path, params) =>
this._request({
url: path,
method: 'delete',
params
});
post = (path, data, params) =>
this._request({
url: path,
method: 'post',
data,
params
});
put = (path, data, params) =>
this._request({
url: path,
method: 'put',
data,
params
});
patch = (path, data, params) =>
this._request({
url: path,
method: 'patch',
data,
params
});
_request(config) {
this.setState({loading: true});
const fullPath = `${this.axiosInstance.defaults.baseURL}/${config.url}`;
// Unique cache key based on request method, url, query params and more
const cacheKey = hash({path: fullPath, ...config});
// We dont have a request in transit already, go ahead and make one
if(!this.requestCache.has(cacheKey)) {
this.requestCache.set(
cacheKey,
this.axiosInstance.request(config)
// It's possible to set a delay for all requests when using this HOC, nice for debug / dummy data
.then(response => this._delayResponse(response, delay))
.then(data => {
// Clear our request cache - we are done, let others do their stuff
this.requestCache.delete(cacheKey);
this.setState({loading: false});
return data;
})
.catch(err => {
// TODO: delay error responses
this.requestCache.delete(cacheKey);
this.setState({loading: false});
return Promise.reject(err);
})
);
}
return this.requestCache.get(cacheKey);
}
_delayResponse(response, delay = 0) {
return new Promise(resolve => {
setTimeout(() => {
resolve(response.data || {});
}, delay);
});
}
render() {
const {loading} = this.state;
const {
get,
remove,
post,
put,
patch
} = this;
return (
<WrappedComponent
loading={loading}
get={get}
delete={remove}
post={post}
put={put}
patch={patch}
/>
);
}
}
return HasAxiosClient;
};
export default hasAxiosClient;
import React, {Component} from 'react';
import Container from '../Container';
import {compose} from 'redux';
import hasEndpoint from '../shared/hoc/hasEndpoint';
const EnhancedContainer = compose(
hasAxiosClient('https://jsonplaceholder.typicode.com', 4000)
)(Container);
class MockContainerPage extends Component {
render() {
return (
<EnhancedContainer {...this.props} />
);
}
}
export default MockContainerPage;
import React, {Component} from 'react';
import Container from '../Container';
import {compose} from 'redux';
import hasEndpoint from '../shared/hoc/hasEndpoint';
const EnhancedContainer = compose(
hasAxiosClient('/api/v1', 0)
)(Container);
class ContainerPage extends Component {
render() {
return (
<EnhancedContainer {...this.props} />
);
}
}
export default ContainerPage;
import React, {Component} from 'react';
import PropTypes from 'prop-types';
class Container extends Component {
static propTypes = {
loading: PropTypes.bool,
data: PropTypes.any,
get: PropTypes.func,
delete: PropTypes.func,
post: PropTypes.func,
put: PropTypes.func,
patch: PropTypes.func
}
get = () => {
// "local" loading tracking
this.setState({loading: true});
this.props.get('todos').then(data => this.setState({data, loading: false});
}
// post, delete, put, patch
render() {
const hocIsLoading = this.props.loading;
const componentIsLoading = this.state.loading;
const {get} = this.props;
return (
<button onClick={get}>GET request</button>
);
}
}
import React from 'react';
import ReactDOM from 'react-dom';
import ContainerPage from './ContainerPage';
import MockContainerPage from './MockContainerPage';
// if you want real data in the "container" component:
ReactDOM.render(<ContainerPage />, document.querySelector('#app'));
// if you want mock data in the "container" component:
// ReactDOM.render(<MockContainerPage />, document.querySelector('#app'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment