The problem(s)

There has been some work done to standardize APIs on the back-end, but front-end developers have continued to solve and solve again the problems of fetching data, caching it or otherwise minimizing re-fetches, and managing related UI state.

For requesting data from your API, do you use superagent, fetch, isomorphic-fetch, XMLHttpRequest, oboe, and/or an abstraction on top of these?

For API data caching and state management do you use Redux, React component state, Flux, other?

Do you update your UI optimistically? If so, what patterns do you use for reverting data and updating the UI?

2. GraphQL + Relay and comparable solutions show tremendous promise as a way forward eventually, but a) configuration can be very involved and not intuitive, b) not everyone is now or will soon be in a place to convert existing APIs, and c) many APIs need to support lots of different kinds of clients, some of which may not work so easily with GraphQL. REST will be a common way to implement APIs for a long while to come.

A solution

Use Jetset. It abstracts away all fetching, caching, and state management details. It injects API data right into your components in a safe and optimized way so that you can focus on composing your views.

With almost no configuration, you get create(), list(), get(), update(), and delete() for a given resource, passed in as props directly to your components. For example:

import { apiDecorator } from 'jetset'; const Api = apiDecorator({

url: 'http://domain.com/api',

users: '/users'

}) const MyComponent = Api( props => {

const users = props.users.list();

return (

<div>

{ users.data.map(({ data: user }) => <div>{ user.fullName }</div> )}

</div>

)

})

Here we have a higher-order component that translates your url and resource into props that expose methods for fetching, caching, and rendering data.

Note that we’re calling list() while rendering. The first time it gets called it actually triggers a fetch and caches the data so that on subsequent renders it just reads the data from cache rather than fetching again. You do not need to set up your fetch or trigger actions or manage any state on your own whatsoever. Though there are ways to hook into the various stages of the fetch lifecycle if you need it. More on that below and in the docs.

All CRUD operations are bundled in by default:

export default Api(({ users }) =>

<div>

{ users.list().data.map( user => (

<div>

<span>{ user.data.name }</span>



<button onClick={() => user.update({ name: 'Pat' }) }>Rename to Pat</button>



<button onClick={ user.delete }>Delete</button>



<button onClick={() => users.get( user.data.id ) }>Get detail</button>

</div>

))}



<button onClick={() => users.create({ name: 'Chris' }) }>Create user named Chris</button>

</div>

)

Key off helpers that indicate the status of the fetch:

const users = props.users.list(); users.isPending ?

<span>Loading...</span> : users.error ?

<span>Error: {list.error.message}</span> : <div>

{ users.data.map( user => <div>{ user.data.name }</div> ) }

</div>

Customize your routes beyond the standard create, list, get, delete:

const routes = {

default: '/users',

onError: error => localStorage.removeItem('some_token')

getUserAlbums: id => ({ method: 'get', route: `/users/${id}/albums`, usesCache: true })

}



<Api ... users={{ routes }}>

And lots more! See https://github.com/DigitalGlobe/jetset and its more detailed docs for more info.