React Easy State is the fruition of my two years long journey with ES6 Proxies and meta programming. It is also a state management library for React.
Easy State is a React state management library with no core philosophy — like functional programming or immutability — except for one thing. It aims to be as close to vanilla JavaScript as possible … and it got pretty close. You can store your state in simple objects, which may be used and mutated in any way you want.
Behind the scenes the state stores are wrapped by ES6 Proxies, which intercept all basic operations — like property get and set — and add a touch of reactive magic. They register which part of which store is used in which component’s render and automatically re-render the component when necessary.
The cool thing about Proxies is transparency. From your point of view none of this is visible. You just have to deal with plain objects and React components and let Easy State keep them in sync for you.
“Hello World!” is boring. Let’s make a dumb stopwatch instead.
First we have to create a clock, which serves as our state store. It should save how many times it ticked so far and it should be startable and stoppable.
import { store } from 'react-easy-state'
const clock = store({ ticks: 0, start () { clock.intervalId = setInterval(() => clock.ticks++, 10) }, stop () { clock.intervalId = clearInterval(clock.intervalId) }})
export default clock
As I promised you, this is vanilla JavaScript … except for store
. store
is one of the two functions of Easy State and it wraps objects with transparent, reactive Proxies.
Rule #1: Always wrap state stores with **store**
.
We will also need a view to display our clock. Let’s go with the simplest option: a function component.
import React from 'react'import { view } from 'react-easy-state'import clock from './clock'
function StopWatch () { const { ticks, start, stop } = clock
return ( <div> <div>{ticks}</div> <button onClick={start}>Start</button> <button onClick={stop}>Stop</button> </div> )}
export default view(StopWatch)
Not much to explain. The clock store is a normal object, StopWatch is a normal React component. The only strange thing is view
, which is the other function of Easy State. view
turns your component reactive and re-renders it when a store property - used by its render - mutates.
Rule #2: Always wrap components with **view**
.
You can see the demo below.
I told you, it’s dumb… Time to make it shiny.
We have to add a bunch of features to the clock store. It should display the elapsed ticks in a nicer format, it should know when it is ticking and it should be resettable.
import { store } from 'react-easy-state'import moment from 'moment'
const clock = store({ ticks: 0, start () { clock.intervalId = setInterval(() => clock.ticks++, 10) }, stop () { clock.intervalId = clearInterval(clock.intervalId) }, get time () { const time = moment(0).millisecond(clock.ticks * 10)
return { seconds: time.format('mm:ss'), fraction: time.format('SS') } }, get isTicking () { return clock.intervalId !== undefined }, toggle () { clock.isTicking ? clock.stop() : clock.start() }, reset () { clock.ticks = 0 clock.stop() }})
export default clock
Nothing very surprising, just a bunch of new JS code. Keeping the store framework independent is a nice idea. It lowers the barrier of entry for new devs and makes switching frameworks easier.
Let’s move on with the new StopWatch component. It displays the nicely formatted time from the clock and adds a reset functionality.
import React from 'react'import { view } from 'react-easy-state'import clock from './clock'
function StopWatch () { const { time, toggle, reset, isTicking } = clock const label = isTicking ? 'Stop' : 'Start'
return ( <div> <div>{time.seconds}<small>{time.fraction}</small></div> <button onClick={toggle}>{label}</button> <button onClick={reset}>Reset</button> </div> )}
export default view(StopWatch)
The live demo is here. Still not rocket science, but it can compete with the big guys. Google “stopwatch”, if you don’t believe me.
Hopefully you start to see the pattern form the above examples. Easy State has two simple rules:
store
.view
.Apart from these two, you have total freedom. You can access and manage your state however you want to.
Easy State is a stalker, it secretly keeps track of two things:
store
tracks every property get and set operation on the state stores,view
tracks the currently running render function.When a store property is used inside a render function, it is paired with the render and saved in a tuple. Later — when the same property is mutated — it looks up all of its saved renders and executes them. This way the view is always kept in sync with the state.
Let’s move inside our stopwatch and see what’s going on there.
view
saves the fact that StopWatch is currently rendering.time
, isTicking
, ticks
and intervalId
properties of the clock store during its render. All of these get operations are intercepted by the store
Proxy and Easy State takes mental notes: StopWatch is using these properties to render.intervalId
. The set operation is intercepted by the store
Proxy, which realizes that intervalId
(and isTicking
) changed. Easy State re-renders every component, which relies on these properties. In our case, this means StopWatch.ticks
every 10 milliseconds. Easy State knows that StopWatch uses ticks
and re-renders the component every time ticks
is incremented.Easy State is based on an old idea — called transparent reactive programming — which is used by VueJS and MobX for example. The innovation lies in the implementation, not the concept.
Both MobX and VueJS use ES5 getters and setters to track property access and mutation on the stores. This approach has limitations — like arrays and expando properties — which require workarounds from your side.
By using ES6 Proxies, Easy State can finally complete the magic of transparent reactivity. It can track anything from dynamic properties to delete operations, inherited properties, iteration, enumeration and property accessors. It won’t ruin the reactive fun with exotic bugs and workarounds.
If this article captured your interest please help by sharing it. Also check out the Easy State repo and leave a star before you go.
Thank you!
Originally published at blog.risingstack.com on January 24, 2018.