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.

The joy of simplicity

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.

Coding a stopwatch

"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.

clock_1

I told you, it's dumb... Time to make it shiny.

Become a master watchmaker

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.

clock_2

The two rules of Easy State

Hopefully you start to see the pattern form the above examples. Easy State has two simple rules:

  1. Always wrap your state stores with store.
  2. Always wrap your components with view.

Apart from these two, you have total freedom. You can access and manage your state however you want to.

Under the hood

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.

  1. StopWatch is rendered for the first time. view saves the fact that StopWatch is currently rendering.
  2. StopWatch uses the 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.
  3. The user presses the Start button, which starts an interval and sets 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.
  4. The interval increments ticks every 10 milliseconds. Easy State knows that StopWatch uses ticks and re-renders the component every time ticks is incremented.

Why should I try it?

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!