Introducing Microcosm: Our Data Layer For React
Microcosm is our general tool for managing state, splitting up large apps, and structuring our React code.
One of my favorite things about working in client-services is the interval with which we start new work. As a React shop, this means we build a lot of new apps from the ground up.
Along the way, we’ve distilled what we’ve learned and baked it into a tool that I, finally, want to talk about.
Microcosm is our general purpose tool for keeping React apps organized. We use it to work with application state, split large projects into manageable chunks, and as the guiding star for our application architecture.
In this post, I’ll provide a high level overview of Microcosm and some of the features I find particularly valuable. Don’t forget to check out the project on Github!
At a glance
Microcosm was born out of the Flux mindset. From there it draws similar pieces:
Actions
Actions are a general abstraction for performing a job. In Microcosm, actions move through a standard lifecycle: (open
, update
, resolve
, reject
, cancel
).
Actions can process a variety of data types out of the box. For example, a basic networking request might look like:
However they can also expose fine grained control over their lifecycle:
Domains define the rules in which actions are converted into new state. Conceptually they are sort of like stores in Flux, or reducers in Redux. They register to specific actions, performing some transformation over data:
Basically: mount a data processor at repo.state.users
that appends a user to a list whenever getUser
finishes.
Effects
Effects provide an outlet for side-effects after domains have updated state. We use them for flash notifications, persistence in local storage, and other behavior that doesn’t relate to managing state:
New here: Domains and Effects can subscribe to specific action states. The effect above will listen for when getUser
fails, alerting the user that something went wrong.
Altogether, this looks something like:
It’s 2017, why aren’t you using Redux?
We do! As a client services company, we use whatever tool best serves our clients. In some cases, that means using Redux, particularly if it’s a client preference or the existing framework for a project.
However there are a few features of Microcosm that we think are compelling:
Action State
We’ve found that, when actions are treated as static events, the state around the work performed is often discarded. Networking requests are a story, not an outcome.
What if a user leaves a page before a request finishes? Or they get tired of a huge file uploading too slowly? What if they dip into a subway tunnel and lose connectivity? They might want to retry a request, cancel it, or just see what’s happening.
Microcosm makes this easier by providing a standard interface for interacting with outstanding work. For example, let’s say we want to stop asking for data if a user no longer cares about the related presentation:
Assuming we give this component a Microcosm “repo” prop, and a list of planets, this component will fetch planets data, stopping whenever the component unmounts. We don’t need to care if the request is represented by a Promise, Observable, error-first callback, etc.
Reducing boilerplate
Since actions move through consistent states, we can leverage these constraints to build boilerplate reducing React components for common problems. For example, we frequently need to dispatch an action to perform some task, so Microcosm ships with an <ActionButton />
component:
Because the lifecycle is predictable, we can expose hooks to make further improvements around that lifecycle:
This makes one-time, use-case specific display requirements, like error reporting, or tracking file upload progress easy. In a lot of cases, the data layer doesn’t need to get involved whatsoever. This makes state management simpler — it doesn’t need to account for all of the specific user experience requirements within an interface.
Optimistic updates — Taking a historical approach
Actions are placed within a history of all outstanding work. This is maintained by a tree:
Taken from the Chatbot example.
Microcosm will never clean up an action that precedes incomplete work. When an action moves from open
to done
, or cancelled
, the historical account of actions rolls back to the last state, rolling forward with the new action states. This makes optimistic updates simpler because action states are self cleaning; interstitial states are reverted automatically:
In this example, as chat messages are sent, we optimistically update state with the pending message. At this point, the action is in an open
state. The request has not finished.
On completion, when the action moves into error
or done
, Microcosm recalculates state starting from the point prior to the open
state update. The message stops being in a loading state because, as far as Microcosm is now concerned, the open
status never occurred.
Separating responsibility with Presenters
The Presenter
addon is a special React component that can build a view model around a given Microcosm state, sending it to child "passive view" components.
When a Presenter is instantiated, it creates a fork of a Microcosm instance. A fork is a “downstream” Microcosm that gets the same state updates as the original but can add additional Domains and Effects without impacting the “upstream” Microcosm.
This sandbox allows you to break up complicated apps into smaller sections. Share state that you need everywhere, but keep context specific state isolated to a section of your application:
In this example, we can keep a users email preferences local to this component. We could even lazy load this entire feature, state management included, using a library like react-loadable. For large applications, we’ve found this is essential for keeping build sizes down.
David wrote a fantastic article that goes into further detail on this subject.
What’s next
At Viget, we’re excited about the future of Microcosm, and have a few areas we want to focus on in the next few months:
- Developer tools. First class developer tools have become the baseline for JavaScript frameworks. Since Microcosm “knows” more about the state of actions, presenters, and other pieces, we’re excited about opportunities to build fantastic tooling.
- Support for Preact, Glimmer, Vue, and other frameworks. We’d love to stop calling our apps “React apps”. What would it look like for the presentation layer to take on less responsibility?
- Observables. The similarities between Actions and Observables is striking. We’re curious about how we can use Observables more under the hood to provide greater interoperability with other tools.
So check it out! We’re always willing to accept feedback and would love to hear about how you build apps.
Nate is a senior developer working from New York City, where he focuses on client-side application development. Most days, you can find him neck-deep in JavaScript working with clients such as The Nature Conservancy and the Wildlife Conservation Society.
Originally published at www.viget.com.