The Ideas Behind React Easy State: Utilizing ES6 Proxies
Tags: react, javascript, webdev, state
Published: 2018-10-23 12:03:59

---
title: The Ideas Behind React Easy State: Utilizing ES6 Proxies
published: true
description: A brief description of how I used ES6 Proxies to create a transparent reactive state management library and what difficulties I had to face to polish it over time.
tags: react, javascript, webdev, state
cover_image: https://thepracticaldev.s3.amazonaws.com/i/q6kot1azkts81hyz367p.png
---

Front-end developers often refer to transparent reactivity — at the core of MobX, Vue or React Easy State — as magic, but there is nothing magical about it. It is based on a very simple idea, which can be demonstrated with the following snippet.

```jsx
import React from 'react'
import { view, store } from 'react-easy-state'

const notepad = store({
  author: 'Mr. Note Maker',
  notes: []
})

const NotesApp = view(() =>
  notepad.notes.map(note => <Note note={note} />)
)

const Note = view(({ note }) =>
  <p>{note.text} by {notepad.author}</p>
)
```

You can perfectly define when you expect `NotesApp` and `Note` to re-render: when a new note is added or removed and when the author or a note’s text is modified. Luckily this conclusion was not driven by complex human intuition, but simple programmable if-else logic.

> If a part of a state store — which is used inside a component’s render— mutates re-render the component to reflect the new state.

Your brain is creating the following ternary relations about properties of objects — used inside render methods.

| object        | property      | component  |
|:-------------:|:-------------:|:----------:|
| appStore      | notes         | NotesApp   |
| notes array   | length        | NotesApp   |
| note object   | text          | Note       |
| appStore      | author        | Note       |

When a property of an object is modified you subconsciously collect all of the components which belong to that `(object, property)` pair. Let’s turn this process into code!

*The rest of the article assumes you have a basic understanding of ES6 Proxies and React Easy State. If you don’t know what I am talking about, a quick look at the [MDN Proxy docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) and the [React Easy State](https://github.com/solkimicreb/react-easy-state) repo is enough to go on.*

## Making a Reactive Core

In order to construct the `(object, property, component)` relations, we have to know which objects and properties do `NotesApp` and `Note` use during their renders. A developer can tell this by a glance at the code, but a library can not.

We also need to know when a property of an object is mutated, to collect the related components from the saved relations and render them.

Both of these can be solved with ES6 Proxies.

```js
import { saveRelation, renderCompsThatUse } from './reactiveWiring'

export function store (obj) {
  return new Proxy(obj, traps)
}

const traps = {
  get (obj, key) {
    saveRelation(obj, key, currentlyRenderingComp)
    return Reflect.get(obj, key)
  },
  set (obj, key, value) {
    renderCompsThatUse(obj, key)
    return Reflect.set(obj, key, value)
  }
}
```

The `store` Proxy intercepts all property get and set operations and — respectively — builds and queries the relationship table.

There is one big question remaining: what is `currentlyRenderingComp` in the get trap and how do we know which component is rendering at the moment? This is where `view` comes into play.

```js
let currentlyRenderingComp = undefined

export function view (Comp) {
  return class ReactiveComp extends Comp {
    render () {
      currentlyRenderingComp = this
      super.render()
      currentlyRenderingComp = undefined
    }
  }
}
```

`view` wraps a component and instruments its render method with a simple logic. It sets the `currentlyRenderingComp` flag to the component while it is rendering. This way we have all the required information to build the relations in our get traps. `object` and `property` are coming from the trap arguments and `component` is the `currentlyRenderingComp` — set by `view`.

Let’s get back to the notes app and see what happens in the reactive code.

```jsx
import React from 'react'
import { view, store } from 'react-easy-state'

const notepad = store({
  author: 'Mr. Note Maker',
  notes: []
})

const NotesApp = view(() =>
  notepad.notes.map(note => <Note note={note} />)
)

const Note = view(({ note }) =>
  <p>{note.text} by {notepad.author}</p>
)
```

1. `NotesApp` renders for the first time.
2. `view` sets `currentlyRenderingComp` to the `NotesApp` component while it is rendering.
3. `NotesApp` iterates the `notes` array and renders a `Note` for each note.
4. The Proxy around `notes` intercepts all get operations and saves the fact that `NotesApp` uses `notes.length` to render. It creates a `(notes, length, NotesApp)` relation.
5. The user adds a new note, which changes `notes.length`.
6. Our reactive core looks up all components in relation with `(notes, length)` and re-renders them.
7. In our case: `NotesApp` is re-rendered.

## The Real Challenges

The above section shows you how to make an optimistic reactive core, but the real challenges are in the numerous pitfalls, edge cases, and design decisions. In this section I will briefly describe some of them.

### Scheduling the Renders

A transparent reactivity library should not do anything other than constructing, saving, querying and cleaning up those `(object, property, component)` relations on relevant get/set operations. Executing the renders is not part of the job.

Easy State collects stale components on property mutations and passes their renders to a scheduler function. The scheduler can then decide when and how to render them. In our case the scheduler is a dummy `setState`, which tells React: ‘I want to be rendered, do it when you feel like it’.

```js
// a few lines from easy-state's source code
this.render = observe(this.render, {
  scheduler: () => this.setState({}),
  lazy: true
})
```

Some reactivity libraries do not have the flexibility of custom schedulers and call `forceUpdate` instead of `setState`, which translates to: ‘Render me now! I don’t care about your priorities’.

This is not yet noticeable — as React still uses a fairly simple render batching logic —but it will become more significant with the introduction of React's new async scheduler.

### Cleaning Up

Saving and querying ternary relations is not so difficult. At least I thought so until I had to clean up after myself.

If a store object or a component is no longer used, all of their relations have to be cleaned up. This requires some cross references — as the relations have to be queryable by `component`, by `object` and by `(object, property)` pairs. Long story short, I messed up and the reactive core behind Easy State leaked memory for a solid year.


![Memory leak](https://thepracticaldev.s3.amazonaws.com/i/iau9fu624z8z18129qft.png)


After numerous ‘clever’ ways of solving this, I settled with wiping every relation of a component before all of its renders. The relations would then build up again from the triggered get traps — during the render.

This might seem like an overkill, but it had a surprisingly low performance impact and two huge benefits.

1. I finally fixed the memory leak.
2. Easy State became adaptive to render functions. It dynamically un-observes and re-observes conditional branches — based on the current application state.

```jsx
import React from 'React'
import { view, store } from 'react-easy-state'

const car = store({
  isMoving: false,
  speed: 0
})

function Car () {
  return car.isMoving ? <p>{car.speed}</p> : <p>The car is parking.</p>
}

export default view(Car)
```

`Car` is not — needlessly re-rendered on `speed` changes when `car.isMoving` is false.

### Implementing the Proxy Traps

Easy State aims to augment JavaScript with reactivity without changing it in a breaking way. To implement the reactive augmentation, I had to split basic operations into two groups.

- Get-like operations retrieve data from an object. These include enumeration, iteration and simple property get/has operations. The `(object, property, component)` relations are saved inside their interceptors.

- Set-like operations mutate data. These include property add, set and delete operations and their interceptors query the relationship table for stale components.

| get-like operations | set-like operations |
|:-------------------:|:-------------------:|
| get                 | add                 |
| has                 | set                 |
| enumeration         | delete              |
| iteration           | clear               |

After determining the two groups, I had to go through the operations one-by-one and add reactivity to them in a seamless way. This required a deep understanding of basic JavaScript operations and the [ECMAScript standard](https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf) was a huge help here. Check it out if you don’t know the answer to all of the questions below.

- What is a property descriptor?
- Do property set operations traverse the prototype chain?
- Can you delete property accessors with the delete operator?
- What is the difference between the target and the receiver of a get operation?
- Is there a way to intercept object enumeration?

### Managing a Dynamic Store Tree

So far you have seen that `store` wraps objects with reactive Proxies, but that only results in one level of reactive properties. Why does the below app re-render when `person.name.first` is changed?

```jsx
import { store, view } from 'react-easy-state'

const person = store({
  name: { first: 'Bob', last: 'Marley' }
})

export default view(() => 
  <div>{person.name.first + person.name.last}</div>
)
```

To support nested properties the ‘get part’ of our reactive core has to be slightly modified.

```jsx
import { saveRelation } from './reactiveWriring'

const storeCache = new WeakMap()

export function store (obj) {
  const reactiveStore = storeCache.get(obj) || new Proxy(obj, traps)
  storeCache.set(obj, reactiveStore)
  return store
}

const traps = {
  get (obj, key) {
    saveRelation(obj, key, currentlyRenderingComp)
    const result = Reflect.get(obj, key)
    if (typeof result === 'object' && currentlyRenderingComp) {
      return store(result)
    }
    return storeCache.get(result) || result
  }
}
```

The most important section is the final `if` block between line 15–18.

- It makes properties reactive lazily — at any depth — by wrapping nested objects in reactive Proxies at get time.

- It only wraps objects, if they are used inside a component’s render — thanks to the `currentlyRenderingComp` check. Other objects could never trigger renders and don’t need reactive instrumentation.

- Objects with a cached reactive wrapper are certainly used inside component renders, since the `currentlyRenderingComp` check— at line 15 — passed for them previously. These objects may trigger a reactive render with property mutation, so the get trap has to return their wrapped versions.

These points — and the fact that relations are cleaned up before every render — results in a minimal, adaptive subset of nested reactive store properties.

### Monkey Patching Built-in Objects

Some built-in JavaScript objects — like ES6 collections — have special ‘internal slots’. These hidden code pieces can't be altered and they may have expectations towards their `this` value. If someone calls them with an unexpected `this`, they fail with an `incompatible receiver error`.

![Error](https://thepracticaldev.s3.amazonaws.com/i/2l75o0nhylaivvxjp4x5.png)

Unfortunately, Proxies are also invalid receivers in these cases and Proxy wrapped objects throw the same error.

![Proxy error](https://thepracticaldev.s3.amazonaws.com/i/nhjj3ysgafsosfhip0cq.png)

To work around this, I had to find a viable alternative to Proxies for built-in objects. Luckily they all have a function based interface, so I could resort to old-fashioned monkey patching.

The process is very similar to the Proxy based approach. The built-in’s interface has to be split into two groups: set-like and get-like operations. Then the object’s methods have to be patched with the appropriate reactivity logic — namely constructing and querying the reactive relations.

### A Bit of Intuition

I was a bit overgeneralizing when I stated that the reactive core is made with cold logic only. In the end, I had to use some intuition too.

Making everything reactive is a nice challenge, but goes against user expectations. I collected some meta operations — that people don’t want to be reactive — and left them out of the fun.

| none reactive get-like operations  | none reactive set-like operations  |
|:----------------------------------:|:----------------------------------:|
| Object.getOwnPropertyDescriptor()  | Object.defineProperty()            |
| Well-known Symbol keyed properties | Well-known Symbol keyed properties |

These choices were made by intuition during my usage test rounds. Others might have a different approach to this, but I think I collected a sensible subset of the language. Every single operation in the above table has a good reason not to be reactive.

## Conclusion

The reactive core — implemented in this article — is not in the source of React Easy State. In reality, the reactive logic is in a more general library — called the [Observer Utility](https://github.com/nx-js/observer-util) — and Easy State is just a thin port for React. I intentionally simplified this to make it more digestible, but the presented ideas are still the same. I hope you learned something new if you made it so far!

*If this article captured your interest please help by sharing it. Also check out the [Easy State repo](https://github.com/solkimicreb/react-easy-state) and leave a star before you go.*

*Thanks!*
*(This article was originally published on [Medium](https://itnext.io/the-ideas-behind-react-easy-state-901d70e4d03e))*