Skip to content

Instantly share code, notes, and snippets.

@ryanflorence
Last active October 8, 2017 17:24
Show Gist options
  • Save ryanflorence/544e1c2def634f8f8e658fed712a86ae to your computer and use it in GitHub Desktop.
Save ryanflorence/544e1c2def634f8f8e658fed712a86ae to your computer and use it in GitHub Desktop.
import { Component } from 'react'
import { createStore, combineReducers } from 'redux'
import parseLinkHeader from 'parse-link-header'
const START = 'start'
const SUCCEED = 'succeed'
const ERROR = 'error'
const inflight = (state={}, action) => (
((state) => (
action.type === START ? (
(state[action.pathname] = true, state)
) : action.type === SUCCEED || action.type === ERROR ? (
(delete state[action.pathname], state)
) : state
))({ ...state })
)
const links = (state={}, action) => (
((state) => (
action.type === SUCCEED ? (
(state[action.pathname] = action.link, state)
) : action.type === ERROR ? (
(delete state[action.pathname], state)
) : state
))({ ...state })
)
const data = (state={}, action) => (
((state) => (
action.type === SUCCEED ? (
(state[action.pathname] = action.data, state)
) : action.type === ERROR ? (
(delete state[action.pathname], state)
) : state
))({ ...state })
)
const errors = (state={}, action) => (
((state) => (
action.type === ERROR ? (
(state[action.pathname] = action.error)
) : action.type === START ? (
(delete state[action.pathname], state)
) : state
))({ ...state })
)
const store = createStore(combineReducers({
inflight,
data,
errors,
links
}))
const stringify = (obj) => (
Object.keys(obj).map((key) => (
`${key}=${obj[key]}`
)).join('&')
)
class Fetch extends Component {
state = {
error: null,
data: null,
loading: null
}
componentWillUnmount() {
this.unmounted = true
}
componentDidMount() {
store.subscribe(() => {
if (!this.unmounted) {
const { errors, inflight, data, links } = store.getState()
const pathname = this.getPathname()
this.setState({
error: errors[pathname] || null,
data: data[pathname] || null,
loading: inflight[pathname] || null,
links: links[pathname] || null
})
}
})
this.fetch()
}
componentWillReceiveProps(nextProps) {
const nextPathname = this.getPathname(nextProps)
const currentPathname = this.getPathname()
if (nextPathname !== currentPathname) {
this.fetch(nextProps)
}
}
getPathname(props) {
const { url, query } = props || this.props
let pathname = url
if (query)
pathname += '?'+stringify(query)
return pathname
}
fetch(props) {
const pathname = this.getPathname(props)
const { inflight, data } = store.getState()
const alreadyCached = data[pathname]
const alreadyInFlight = inflight[pathname]
if (!alreadyCached && !alreadyInFlight) {
store.dispatch({ type: START, pathname })
fetch(pathname).then(res => {
const link = res.headers.get('Link')
const json = res.json()
return Promise.all([ json, link ? parseLinkHeader(link) : null ])
}).then(([ data, link ]) => {
store.dispatch({ type: SUCCEED, pathname, data, link })
}, (error) => (
store.dispatch({ type: ERROR, pathname, error })
))
}
}
render() {
return this.props.children(this.state)
}
}
export default Fetch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment