# Computed Properties

## Singular computations

[Entity](https://dataclient.io/rest/api/Entity.md) classes are just normal classes, so any common derived data can just be added as
getters to the class itself.

```typescript
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](https://github.com/anywhichway/nano-memoize).

```typescript
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](https://dataclient.io/rest/guides/network-transform.md#deserializing-fields) to a more useful form like [Temporal.Instant](https://tc39.es/proposal-temporal/docs/instant.html) or [BigNumber](https://github.com/MikeMcl/bignumber.js), you can use
> the declarative [static schema](https://dataclient.io/rest/guides/network-transform.md#deserializing-fields).
>
> ```typescript
> 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](https://dataclient.io/rest/api/Query.md) can be used for computations of derived data from more than
one entity. We generally call these aggregates.

```ts title="resources/User"
import { Entity, resource } from '@data-client/rest';

export class User extends Entity {
  id = '';
  name = '';
  isAdmin = false;
}
export const UserResource = resource({
  path: '/users/:id',
  schema: User,
});
```

```tsx title="UsersPage"
import { All, Query, schema } from '@data-client/rest';
import { useQuery, useSuspense } from '@data-client/react';
import { UserResource, User } from './resources/User';

const getUserCount = new Query(
  new All(User),
  (entries, { isAdmin } = {}) => {
    if (isAdmin !== undefined)
      return entries.filter(user => user.isAdmin === isAdmin).length;
    return entries.length;
  },
);

function UsersPage() {
  useSuspense(UserResource.getList);
  const userCount = useQuery(getUserCount);
  const adminCount = useQuery(getUserCount, { isAdmin: true });
  // this should never happen since we suspense but typescript does not know that
  if (userCount === undefined) return null;
  return (
    <div>
      <div>Total users: {userCount}</div>
      <div>Total admins: {adminCount}</div>
    </div>
  );
}
render(<UsersPage />);
```
