Skip to content

Instantly share code, notes, and snippets.

@gilbert
Last active February 7, 2018 23:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gilbert/8d17d675950265a188f846cbf0bf7c23 to your computer and use it in GitHub Desktop.
Save gilbert/8d17d675950265a188f846cbf0bf7c23 to your computer and use it in GitHub Desktop.
React GET Data Dependencies helper
//
// Our main data fetching tool.
// This fetches an object of promise makers ONLY ON THE CLIENT SIDE,
// and manages loading and data states for each key.
//
import React from 'react'
import http from './http'
export default (promisesMakers, options={}) => Component =>
class FetchHOC extends React.Component {
constructor(props) {
super(props)
this._isMounted = false
this.state = {
loading: true, // true if any dep is loading
error: null, // populated with first dep error if any
}
for (let key in promisesMakers) {
//
// Initialize with loading to ensure child component doesn't try to use data
//
this.state[key+'Loading'] = true
}
}
componentWillMount() {
// Only fetch data in the browser
if ( ! process.browser ) return;
this._isMounted = true
for (let key in promisesMakers) {
let fetch = () => {
if ( this._isMounted !== true || this.state[key+'Loading'] === true ) return;
this.state[key+'Loading'] = true
this.state[key+'Error'] = null
var fetcher = promisesMakers[key]
if ( typeof fetcher === 'string' ) {
let fetchUrl = fetcher
fetcher = () => fetchUrl
}
if ( ! (typeof fetcher === 'function') ) {
throw new Error(`[WithData] Value of ${key} is not a function.`)
}
var promise = fetcher(this.props)
if ( typeof promise === 'string' ) {
// Function returned a url to GET
promise = http.get(promise).then( res => res.data )
}
this.state[key+'Promise'] = promise
return promise.then(
data => {
this.state[key+'Loading'] = false
this.state[key] = data
this.calcOveralLoadingStatus()
},
error => {
console.log('[DataDeps] Error', key, error)
this.state[key+'Loading'] = false
if ( ! this.state.error ) {
this.setState({ error: error })
}
if ( error.response && error.response.status === 404 ) {
this.state[key+'Error'] = '404_not_found'
}
else {
this.state[key+'Error'] = `Unknown error: ${JSON.stringify(error)}`
}
this.calcOveralLoadingStatus()
}
)
}
this.state[key] = null
this.state[key+'Refetech'] = fetch
// Temporarily set to false so `fetch` can run
this.state[key+'Loading'] = false
fetch()
}
this.forceUpdate()
}
calcOveralLoadingStatus() {
var loading = false
for (var key in promisesMakers) {
loading = loading || this.state[key+'Loading']
}
this.setState({ loading: loading })
}
componentWillUnmount() {
this._isMounted = false
}
render() {
var component = <Component {...this.props} {...this.state} />
if ( options.preload ) {
var preloadView = options.preload === true
? <div className="loading-lg"></div>
: options.preload
return this.state.loading ? preloadView : component
}
else {
return component
}
}
}
import dataDeps from '...'
var withData = dataDeps({
contact: (props) => `/api/me/contact/${props.id}`,
contactFields: '/api/me/contact/fields',
})
export default withData((props) => {
if (props.loading) {
// Also available: props.contactLoading, props.contactFieldsLoading
return <div className="loading"></div>
}
if (props.error) {
// props.error is the first failed dep, if any
// Also available: props.contactError, props.contactFieldsError
return <div>Error: {props.error.message}</div>
}
var {contact, contactFields} = this.props
return <div>
Contact: {contact.name}
...
</div>
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment