Last active
November 15, 2018 12:45
-
-
Save dennishn/f3f7468ec20e032a2ff04d86a22c1800 to your computer and use it in GitHub Desktop.
Axios HOC with Cache and configurable delay
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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