MobX computed – reconciliation of chain of computed values

1.6k Views Asked by At

I have three related questions about best practices with “computed”.

TL;DR: What do I need to think of myself and what does reconciliation do out of the box.

Let’s say I load a hefty dataset into a data variable:

data = asyncComputed( await fetch … return data )

I then have convenience computed prop that formats this data:

@computed get timeseries() {
    const data = this.data.get()

    const timeseries = nest()
      .key(d => d.geo)
      .entries(data)

    return timeseries
  }

And a third convenient prop that is calculated on top of the timeseries:

  @computed get extentX() {
    const timeseries = this.timeseries.get()
    const extentX = extent(timeseries, d => d.time)
    return extentX
  }
  1. Is there a risk that a render happens before all these computations are done or does MobX ensure that this is reconciled before?

  2. Is there a better way of structuring this code to avoid chaining of computed props?

  3. Let’s say the first async is triggered when a property (id) is changed on an object and that this object is later updated from the same chain of events:


variable = { id : “my-variable”, extent: undefined} 

data = asyncComputed( await fetch api?id=variable.id ) 

=> computes extent => results in variable.extent = [200, 400] Is there a risk that this can set off an infinite loop or is it safe to update an object property like this?

2

There are 2 best solutions below

0
Shevchenko Viktor On

here are my thoughts on this topic.

  1. Mobx Runs all computations synchronously (Prof see Synchronous execution)
  2. Mobx does lazy evaluation (Prof see Lazy versus reactive evaluation )
  3. When applying @observer it just wraps a render function in autorun (Proof)

From #1 we can make a conclusion that if we have a chain of computed they are evaluated synchronously like: observable -> computed 1 -> computed 2

From #2 we can say that in order to run a side effect - this computed chain will have to end with autorun observable -> computed 1 -> computed 2 -> autorun

This already leads us to point #3. That @observer is a HOC that wraps you Component and controls whenever it needs to be rerendered. This is not now under control of your component.

So answering your questions:

  1. There is no risk that render will happen between computed calculations. It will happen after last computed will call autorun
  2. Chaining of computed is powerful approach and should not be avoided, just used wizdomly.

From docs:

By composing reactive computations it is even possible to automatically transform one graph of data into another graph of data and keep this derivation up to date with the minimum number of patches. This makes it easy to implement complex patterns like map-reduce, state tracking using immutable shared data, or sideways data loading.

  1. Mob has a protection from loops. You will see an console error that changing of observables that cased a flow are not allowed
0
Nick Perkins On

In my experience, building MobX "computed" values on top of other "computed" values is easy and robust, and is a very powerful way to break-up complex data transformations into logical and testable baby-steps.

When using a series of "computeds", each step is a data transformation builds on previous results, and each step has a name. So the result of each step is a named observable that your app can look-at or display on the screen. This makes it easy to write, test, and debug each step separately.

At run-time, your top-level "computeds" get the same magical efficiency and re-calculation as simple "computes", so there is really no down-side. I highly recommend it.