What we love about React is how simple and enjoyable its declarative way of constructing components. And when GraphQL was introduced our minds were blown because we realized: we can do the same with data, describing what our components need and not caring about how and when it will be delivered.
Also, Netflix introduced JSONGraph and Falcor this May. What’s interesting here is that working completely independent from Facebook they came up with very similar ideas. So, basically we can see a solid trend here.
There is one frustrating thing about GraphQL/Falcor though: both of them require certain backend implementation and since we need to get stuff done on existing APIs in our company, we decided to go small steps to the amazing declarative data future.
So what do we have: a bunch of services with REST API, React, very simple Flux implementation and Baobab as the data backbone holding the entire immutable state of our application. I’m not gonna give too much attention to Baobab itself, only that we chose it over Immutable.js at the time because of it’s simplicity and explicitness. Also the maintainer Guillaume is very nice and responsive.
Enough with the intro, let’s jump into the actual code.
Global immutable state
We keep all the data that should be outside components local state in one big global immutable tree which we call just “state”. We use it both in stores to put data inside different parts of the big tree, and in components to consume and use this data.
To illustrate this, let’s say we have an app that shows a list of products that can be sorted ascending or descending. By clicking on each of these products we can see its description.
Here is the structure of our global state:
// selected product id
[ 'ui', 'products', 'selected' ]: <number>// products lists with different sort
[ 'data', 'products', 'list' ]: Array<object>// product info
[ 'data', 'products', 'details', productID ]: <object>
- On the first level we have data and ui layers to separate concerns and make easier reasoning about the app state.
- ui layer usually holds the visual state that can be shared between different components and can be tracked if you have history in your application.
Example: data about dragging element, selected item in the list.
- in data layer we store something we either create ourselves or, which is a more common case, receive from the server.
Example: products list, user information.
- On the second level there is data type, usually reflecting actions-stores pairs. In this simple example there is only “products”, but in the real apps you can see something like “users”, “widgets”, “settings”, etc. — you get the picture.
- Finally, from the third level and deeper we can go bananas and create whatever data paths we want, there are no restrictions.
Here is how we work with data in components (please read the comments):
So all these data dependencies described in components are actually cursor paths (cursors are awesome, read about them here). And what we can do with these cursors is track whenever something updates there and then put it into the components local state. That’s pretty much all DataWatcher does!
Another a bit more complex example is products list. Since we have “sort” option, and products list can be very long, we let server do the sorting and locally store different lists for each sort type (please read the comments):
Talking to the server
There is one missing part though: how do we request this data from the server? We didn’t want to do it from components, so here is what we came up with: with immutable data tree you can not only listen for the data updates, but even when someone trying to access data at the certain cursor path. So we just listen for ‘get’-ters in main wrapper component and if data is not in the state yet, we fire an action to get it. Take a look at the example (please read the comments):
Now we came full circle and all our components listening to this cursor path will receive the data thanks to DataWatcher decorator.
Here is the awkward illustration of data flow described above:
You can play with the example I used in the article here: https://github.com/mistadikay/react-auto-fetching-example/
I intentionally didn’t mention how we save, reset and send parts of global state back to the server, since it probably deserves another article.
So, we showed you how did we manage to simplify the way React components work with data. Though, I’m pretty sure it’s only an intermediate step to the much more awesome reactive and declarative future which is just a few steps away.
UPDATE: following the feedback on this article, we created separate tool for working with immutable state in React. It’s called doob. Also, there is a branch in the example repository using doob. Please keep in mind that it’s still WIP.