Use computed.struct for computed objects

If your @computed getter function constructs an object and returns it, you probably want to mark this computed to be compared structurally.

Normally, if an observable changes, but a computed that observes it returns the same value, then any reactions that observe the computed will not re-fire. This works 'out-of-the-box' for primitive values (i.e. Strings, Numbers, Booleans). But if your computed getter returns an Object (or an Array or a Date), it will be seen as a different value thus causing unnecessary reaction execution.

Marking a computed to be compared structurally solves this problem and is done using the @computed.struct decorator instead of just @computed in MobX 3+. In older versions you would do @computed({ asStructure: true }).

Of course, keep in mind that any change in any property in the returned object (deeply checked) will cause the computed to be deemed as changed and reactions to fire even if the property used by that reaction has not changed! Because of this, it might be better for performance to separate data that changes more frequently into a separate computed object.

That fact that computeds do not cause reactions to re-fire if they don't change often makes computed values the preferred entities to be observed by UI elements (instead of observing the 'source' observable values directly).

For example, if you have a progress indicator tied to a rapidly changing observable, every little change in the observable, even if not visible in the UI, will result in React re-computing the observing component. By putting a computed in between, it can buffer the rapid changes by translating the source values into more visible and less frequently changing values (i.e. integer percents in the case of a progress indicator).

struct computeds still generate a new object/array every time

Every time an observable used by a computed is updated, the computed function is re-evaluated and hence returns a new object/array/date every time. Although the .struct modifier will prevent reactions / observers of this computed from firing, it doesn't change the fact that a new object was returned by the computed. Therefore, if this object/array/date was passed as a prop to a React component, including to a PureComponent or to an @observer, that component's render method will still be re-evaluated.

To prevent this, you can create a computed facade on top of the @computed.struct and pass the facade in React props. The facade computed will not be re-evaluated when the computed.struct it observes hasn't actually changed the data it returns:

@computed.struct get status() { 
  return { key1: someObservable1, key2: someObservable2 };
}
// pass this facade to React components in props:
@computed get statusInfo() {
  return this.status;
}

Last updated