Skip to main content

Computed Properties

Singular computations​

Entity classes are just normal classes, so any common derived data can just be added as getters to the class itself.

import { All, Entity, Query } from '@data-client/rest';

class User extends Entity {
id = '';
firstName = '';
lastName = '';
username = '';
email = '';

get fullName() {
return `${this.firstName} ${this.lastName}`;
}

static key = 'User';
}

If the computations are expensive feel free to add some memoization.

import { All, Entity, Query } from '@data-client/rest';
import memoize from 'nano-memoize';

class User extends Entity {
truelyExpensiveValue = memoize(() => {
// compute that expensive thing!
});
}
tip

If you simply want to deserialize a field to a more useful form like Temporal.Instant or BigNumber, you can use the declarative static schema.

import { All, Entity, Query } from '@data-client/rest';
import BigNumber from 'bignumber.js';

class User extends Entity {
id = '';
firstName = '';
lastName = '';
createdAt = Temporal.Instant.fromEpochMilliseconds(0);
lifetimeBlinkCount = BigNumber(0);

static key = 'User';

static schema = {
createdAt: Temporal.Instant.from,
lifetimeBlinkCount: BigNumber,
};
}

Global computations​

Query derives data from many entities, like a cart's totals. This is known as an aggregate. Define it once beside your resources, then read it anywhere with useQuery().

import { Query } from '@data-client/rest';
import { CartResource } from './resources/Cart';

export const freeShippingAt = 50;

export const getCartTotals = new Query(CartResource.getList.schema, items => {
  const count = items.reduce((sum, item) => sum + item.quantity, 0);
  const subtotal = items.reduce((sum, item) => sum + item.total, 0);
  const toFreeShipping = Math.max(0, freeShippingAt - subtotal);
  const shipping = subtotal && toFreeShipping ? 5 : 0;
  return {
    count,
    subtotal,
    shipping,
    total: subtotal + shipping,
    toFreeShipping,
  };
});
Live Preview

Here the cart badge, free-shipping bar, order summary and checkout button all read getCartTotals. It runs once per change, however many components read it, and returns the same object until a CartItem or Product it read changes. So every view agrees, even mid optimistic update.

Do not compute aggregates in components, where each copy can drift. Instead, put them in a Query.