Skip to content

Instantly share code, notes, and snippets.

@marg51
Created April 20, 2017 09:10
Show Gist options
  • Save marg51/653a558a3b77450e9e4dca84db8724b5 to your computer and use it in GitHub Desktop.
Save marg51/653a558a3b77450e9e4dca84db8724b5 to your computer and use it in GitHub Desktop.
React — Decorators and how to easily manage loading states
import React from "react"
export const promiseable = (self, fnName, attrs) => {
const name = null // not sure how to pass that from outside
const fn = self[fnName] // function being decorated
// we replace the function with a new one
self[fnName] = function() {
this.setState(createObject("loading", name))
// we call the previous function.
const promise = fn.call(this)
if(!promise || !promise.then) {
return this.setState(createObject("failed", name, "you should return a promise"))
}
return promise.then(data => {
this.setState(createObject("loaded", name, data))
return data
}, data => {
this.setState(createObject("failed", name, data))
return data
})
}
return self
}
export const renderWhenLoaded = (self, fnName) => {
const fn = self[fnName]
self[fnName] = function() {
if(this.state.isLoading)
return <div>Loading</div>
if(this.state.hasError) {
console.error(this.state.error)
return <div>Error! {JSON.stringify(this.state.error)}</div>
}
return fn.call(this)
}
return self
}
function createObject(status, name, data) {
let object
switch(status) {
case "loading":
object = {isLoading: true, isLoaded: false, hasError: false}
break
case "loaded":
object = {isLoading: false, isLoaded: true, hasError: false}
break
case "failed":
object = {isLoading: false, isLoaded: false, hasError: true, error: data}
break
default:
throw new Error(`[promise]: status ${status} is not valid. Should be one of loading | loaded | failed`)
}
if(name) {
object = {[name]: object}
}
return object
}
import React, {Component} from 'react';
import {promiseable, renderWhenLoaded} from "./promise"
export default class View extends Component {
constructor() {
super()
this.state = {
data: {a: 1}
}
}
loadData() {
// don't forget to return a promise here
return new Promise((resolve, reject) => {
// we mock a promise here. Just do fetch() for a real life example
setTimeout(() => {
resolve({
a: 2
})
}, 1000)
}).then(data => {
this.setState({data})
})
}
// will do:
// - this.setState({isLoading: true, isLoaded: false, hasError: false}) promise created
// - this.setState({isLoading: false, isLoaded: true, hasError: false}) promise resolved
// - this.setState({isLoading: false, isLoaded: false, hasError: true}) promise rejected
@promiseable
componentDidMount() {
// anything can be in this function, but it has to return a promise
return this.loadData()
}
// will display a spinner while this.state.isLoaded !== true
// will display error message if this.state.hasError === true
@renderWhenLoaded
render() {
return <Calendar getElm={this.getElm.bind(this)} date={this.state.date}/>
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment