2019-08-26 by Alon Bar David

Suspending mobx computation

Summary

With mobx you can reevaluate expressions automatically when objects that those expressions use change.
This is mobx's special sauce and what makes us love it, but sometimes you want to suspend evaluations until something happens.

The usual way to do it with mobx is by subscribing/unsubscribing to changes, but that means you have to manage these kind of state synchronization manually.
Instead we can leverage mobx itself to create a new decorator that can suspend evaluations based on a provided variable.

Background

In a recent project I was making a react-native app that was working constantly in the background to process bluetooth events. When the app was in the background we didn't want to render anything to not overuse the battery.
We were using mobx extensively to drive the logic and views of the app and didn't want to step outside it to manually manage what observers are allowed to run where.

What we really want is the ability to suspend a mobx computation or reaction based on another observable.

Building your own computed decorator

We can build our own @computed decorator that lets us suspend computations arbitrarily:

import { computed} from 'mobx'

const suspended = observable.box(false)
export function setSuspended(value) {
  suspended.set(value)
}
export function suspendableComputed(
  instance: any,
  propertyName: PropertyKey,
  descriptor: PropertyDescriptor,
  ...args
) {
  let cached
  const oldDescriptor = descriptor.get
  descriptor.get = function() {
    if (!suspended.get()) {
      cached = oldDescriptor.apply(this)
    }
    return cached
  }
  return computed(instance, propertyName, descriptor, ...args)
}

so instead of @computed you use suspendableComputed and whenever you want to suspend all computation you simply call setSuspended(true) and anything calling the computed function will get a cached value.
When you want to start evaluating again, you call setSuspended(false) and the last change that should have triggered your computed evaluation is triggered again.

Alternatives

Subscribing and unsubscribing on events

The usual way of handling such things is simply to stop observing values when you want to suspend evaluation. mobx-react does it automatically when a component is no longer rendered, but it can also be done manually.

Prioritized render

If the reason why you want to stop computation is because there are some things that have a high priority to be shown right now and other computations might cause lag, and if you are using react, than you can use the new fiber architecture to give your different renders priority.

Though it's still not a finished api, it's likely to be completed and release very soon. For more information you can look at the wonderful example here

A Library

While we were talking about @computed suspending reactions is also possible with a similar setup, though it's a bit more complicated.
So for that case I've made a small library that adds suspension to both computed and reaction in mobx - mobx-suspend