6.3.0 • Published 8 months ago

proxy-state-tree v6.3.0

Weekly downloads
8,493
License
MIT
Repository
github
Last release
8 months ago

proxy-state-tree

An implementation of the Mobx/Vue state tracking approach, for library authors

THIS IS A WORK IN PROGRESS

Why

There are two main approaches to change detection. immutability and setter/getter interception. Immutability is easy to implement as a library author cause it is really all about comparing values. It is less work for you as a library author, but more work for the developers using the tool. The setter/getter approach, popularized by projects like mobx, mobx-state-tree and vuejs, was traditionally more work for you as a library author, but far less work for the developers consuming your tool. The setter/getter approach also has two other prominent benefits:

  • You mutate your state as normal. No special immutable API or writing your changes in an immutable way
  • Rerendering and recalculation is optimized out of the box as you track exactly the state that is being used

proxy-state-tree allows you to expose a single state tree to your library and track its usage and changes.

Create a tree

import ProxyStateTree from 'proxy-state-tree'

const tree = new ProxyStateTree({})

console.log(tree.get()) // {}

As a library author you would typically expose a mechanism to define the initial state of the application, which you would pass to the ProxyStateTree. You would also expose a way to access the state, hiding the tree.get() from the consumer of your library.

Track access

You can track access to the state by using the trackPaths method.

import ProxyStateTree from 'proxy-state-tree'

const tree = new ProxyStateTree({
  foo: 'bar',
  bar: 'baz'
})
const state = tree.get()

const paths = tree.trackPaths(() => {
  const foo = state.foo
  const bar = state.bar
})

console.log(paths) // [['foo'], ['bar']]

You would typically use this mechanism to track usage of state. For example rendering a component, calculating a a computed value etc. The returned paths array is stored for later usage.

Track mutations

import ProxyStateTree from 'proxy-state-tree'

const tree = new ProxyStateTree({
  foo: 'bar',
  bar: []
})
const state = tree.get()

const mutations = tree.trackMutations(() => {
  state.foo = 'bar2'
  state.bar.push('baz')
})

console.log(mutations)
/*
  [{
    method: 'set',
    path: ['foo'],
    args: ['bar2']  
  }, {
    method: 'push',
    path: ['bar'],
    args: ['baz']
  }]
*/

You would use trackMutations around logic that is allowed to do mutations, for example actions or similar.

React to mutations

import ProxyStateTree from 'proxy-state-tree'

const tree = new ProxyStateTree({
  foo: 'bar',
  bar: []
})

tree.onMutation((mutations) => {
  mutations
  /*
    [{
      method: 'set',
      path: ['foo'],
      args: ['bar2']  
    }, {
      method: 'push',
      path: ['bar'],
      args: ['baz']
    }]
  */
})

const state = tree.get()

const mutations = tree.trackMutations(() => {
  state.foo = 'bar2'
  state.bar.push('baz')
})

You would typically expose onMutation to parts of your application that has tracked some state. For example a component, a computed etc. This allows parts of the system to react to mutations.

Check need to update

import ProxyStateTree from 'proxy-state-tree'

const tree = new ProxyStateTree({
  foo: 'bar',
  bar: 'baz'
})
const state = tree.get()
const paths = tree.trackPaths(() => {
  const foo = state.foo
  const bar = state.bar
})

tree.onMutation((mutations) => {
  const hasMutated = tree.hasMutation(paths, mutations)
})

tree.trackMutations(() => {
  state.foo = 'bar2'
  state.bar.push('baz')
})

Here we combine the tracked paths with the mutations performed to see if this components, computed or whatever indeed needs to run again, doing a new trackPaths.

Dynamic state values

If you insert a function into the state tree it will be called when accessed. The function is passed the proxy-state-tree instance and the path of where the function lives in the tree. The allows you to easily extend functionality with for example a computed concept:

import ProxyStateTree from 'proxy-state-tree'

class Computed {
  constructor(cb) {
    this.isDirty = true;
    this.discardOnMutation = null;
    this.value = null;
    this.paths = null;
    this.cb = cb;
    return this.evaluate.bind(this);
  }
  evaluate(proxyStateTree, path) {
    if (!this.discardOnMutation) {
      this.discardOnMutation = proxyStateTree.onMutation(mutations => {
        if (!this.isDirty) {
          this.isDirty = proxyStateTree.hasMutated(this.paths, mutations);
        }
      });
    }
    if (this.isDirty) {
      this.paths = proxyStateTree.trackPaths(() => {
        this.value = this.cb(proxyStateTree.get());
        this.isDirty = false;
      });
    }
    return this.value;
  }
}

const tree = new ProxyStateTree({
  foo: 'bar',
  upperFoo: new Computed(state => state.foo.toUpperCase()),
})

tree.get().upperFoo // 'BAR'

And this computed only recalculates if "foo" changes.

6.3.0-1688895117677

10 months ago

6.3.0-1687524888860

11 months ago

6.3.0-1689449193734

10 months ago

6.3.0-1689448727006

10 months ago

6.3.0-1687088878294

11 months ago

6.3.0-1688630706634

10 months ago

6.3.0-1688898842314

10 months ago

6.3.0-1688634825676

10 months ago

6.3.0-1686952849485

11 months ago

6.3.0-1688891482797

10 months ago

6.3.0-1688594209920

10 months ago

6.3.0

3 years ago

6.2.0

3 years ago

6.1.0

4 years ago

6.0.2

4 years ago

6.0.1

4 years ago

6.0.0

4 years ago

5.0.1

4 years ago

5.0.0

4 years ago

4.7.0

4 years ago

4.6.0

4 years ago

4.5.0

5 years ago

4.4.3

5 years ago

4.4.2

5 years ago

4.4.1

5 years ago

4.4.0

5 years ago

4.3.0

5 years ago

4.2.0

5 years ago

4.1.3

5 years ago

4.1.2

5 years ago

4.1.1

5 years ago

4.1.0

5 years ago

4.0.0

5 years ago

3.2.0

5 years ago

3.1.0

5 years ago

3.0.0

5 years ago

2.1.0

5 years ago

2.0.2

5 years ago

2.0.1

5 years ago

2.0.0

5 years ago

1.3.1

6 years ago

1.3.0

6 years ago

1.2.0

6 years ago

1.1.0

6 years ago

1.0.2

6 years ago

1.0.1

6 years ago

1.0.0-beta4

6 years ago

1.0.0-beta3

6 years ago

1.0.0-beta2

6 years ago

1.0.0-beta1

6 years ago

1.0.0-alpha8

6 years ago

1.0.0-alpha7

6 years ago

1.0.0-alpha6

6 years ago

1.0.0-alpha5

6 years ago

1.0.0-alpha4

6 years ago

1.0.0-alpha3

6 years ago

1.0.0-alpha2

6 years ago

1.0.0-alpha1

6 years ago

1.0.0

6 years ago