redata
DISCLAIMER: THIS MODULE IS COMPLETELY WORK IN PROGRESS, AND WILL BE FOR THE NEXT FEW DAYS. IF YOU LIKE THE WORK SO FAR, KEEP AN EYE OUT, WE SHOULD BE RELEASING A STABLE VERSION IN A WEEK OR SO.
React data container for isomorphic applications, providing a unified and performant load & reload pattern.
The motivation behind redata is to create a flexible, efficient and simple solution for isomorphic react components to asynchronously load information. A few driving concepts behind redata:
- You should only need to write your loading routines once, and it should run both on the server and client without any added control.
- The component should automatically react (pun intended) to prop changes and potentially reload the data if necessary.
- In case a prop change does not affect the relevance of the already loaded data, the loaded data should be reused automatically.
- Loading data might require a single async operation, but it also might require multiple operations, and you should be able to inspect progress on a granular level if needed, but also be able to quickly understand if everything is loaded, loading, or errored, in case you don't need the granularity.
- It should be useful and easy to use if you're just using React, but also if you're using React + Redux.
- It should be compatible with ReactNative.
- It should sound like a verb, so you can say something like you just need to redata the component, meaning that the data will load & reload automatically and in a consistent fashion.
Installation
$ npm install --save redata
Usage
Here's an example of how you would redata a ShoppingBag
component which requires that the contents of the bag be loaded via a fetchBag()
that already exists, and returns a Promise
:
;;; { const result loading error = thisprops; // loading and error are provided by redata, but you can rename // or even drop any property that you don't need in the mapper if error || loading return null; // you could render an error message or loading respectively return <ul> result </ul> ; } ShoppingBagpropTypes = bagId: PropTypesnumberisRequired loading: PropTypesbool error: PropTypes result: PropTypesarray; // loader propsbagId !== nextPropsbagId // shouldReload policyShoppingBag;
redata
is a Higher Order Component with the following signature:
WrappedComponent
loader
: The function that is responsible for loading the data. Function is invoked with({ props, state })
and should return aPromise
for the data.shouldReload
(optional):- Function that decides whether the data should be reloaded or not, should return
boolean
. - Called every time props or state changes, and defaults to
({ props, nextProps, state, nextState, data }) => false
, meaning that by default the data is only loaded once, and reused until the component is unmounted.data
refers to the result of the last redata.
- Function that decides whether the data should be reloaded or not, should return
mapper
(optional):- Function that is called after the loader, and defaults to an identity function, or
(data) => data
.data
is an object containing:loading
:true
if the loader is still running,false
otherwise.error
: An instance ofError
in case the loader failed,undefined
otherwise.result
: The result of the loader, orundefined
if the loader is still running.
- The return value of this function is spread as props for your component.
- Function that is called after the loader, and defaults to an identity function, or
The nitty-gritty details
So you're interested in understanding how redata works under the hood. That's awesome.
The motivation behind redata is already explained in the introduction, but in order to get it to work, there were a few pitfalls that we had to avoid.
In terms of the server side rendering, the way that redata discovers async dependencies in data is inspired by Apollo
.
Here's a breakdown of how the whole redata flow works:
- Server:
- Client:
redata
component is rendered for the first time.- Checks a global store for any previously loaded data. In React, a really hard to collide and predictable key is stored in
window
by the server-side render, which is then accessed by the client. - If data was loaded, the redata cycle is restarted, by calling the
shouldReload
function, which determines if the data is still good, or if it needs to reload. - If data is still good, the data is returned, which can mean one of two things:
- redata is wrapping a component: data is spread and injected as props into the underlying component.
- redata is not wrapping a component, typically because it is composing a larger redata: the promise that was returned by redata resolves.
- If data is no longer good, the loader is called.