Skip to main content

schema.Collection

Collections define mutable Lists (Array) or Maps (Values).

This means they can grow and shrink. You can add to Collection(Array) with .push or .unshift and Collections(Values) with .assign.

RestEndpoint provides .push, .unshift, .assign and .getPage/ .paginated() extenders when using Collections

Usage

import { getTodos } from './api/Todo';

export default function NewTodo({ userId }: { userId?: string }) {
  const ctrl = useController();
  const [unshift, setUnshift] = React.useState(false);

  const handlePress = async e => {
    if (e.key === 'Enter') {
      const createTodo = unshift ? getTodos.unshift : getTodos.push;
      ctrl.fetch(createTodo, {
        title: e.currentTarget.value,
        userId,
      });
      e.currentTarget.value = '';
    }
  };

  return (
    <div className="listItem nogap">
      <TextInput size="small" onKeyDown={handlePress} />
      <label>
        <input
          type="checkbox"
          checked={unshift}
          onChange={e => setUnshift(e.currentTarget.checked)}
        />{' '}
        unshift
      </label>
    </div>
  );
}
🔴 Live Preview
Store

Options

argsKey(...args): Object

Returns a serializable Object whose members uniquely define this collection based on Endpoint arguments.

import { schema, RestEndpoint } from '@data-client/rest';

const getTodos = new RestEndpoint({
path: '/todos',
searchParams: {} as { userId?: string },
schema: new schema.Collection([Todo], {
argsKey: (urlParams: { userId?: string }) => ({
...urlParams,
}),
}),
});

nestKey(parent, key): Object

Returns a serializable Object whose members uniquely define this collection based on the parent it is nested inside.

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

class Todo extends Entity {
id = '';
userId = '';
title = '';
completed = false;

static key = 'Todo';
}

class User extends Entity {
id = '';
name = '';
username = '';
email = '';
todos: Todo[] = [];

static key = 'User';
static schema = {
todos: new schema.Collection([Todo], {
nestKey: (parent, key) => ({
userId: parent.id,
}),
}),
};
}

nonFilterArgumentKeys?

nonFilterArgumentKeys defines a test to determine which argument keys are not used for filtering the results. For instance, if your API uses 'orderBy' to choose a sort - this argument would not influence which entities are included in the response.

const getPosts = new RestEndpoint({
path: '/:group/posts',
searchParams: {} as { orderBy?: string; author?: string },
schema: new schema.Collection([Post], {
nonFilterArgumentKeys(key) {
return key === 'orderBy';
},
}),
});

For convenience you can also use a RegExp or list of strings:

const getPosts = new RestEndpoint({
path: '/:group/posts',
searchParams: {} as { orderBy?: string; author?: string },
schema: new schema.Collection([Post], {
nonFilterArgumentKeys: /orderBy/,
}),
});
const getPosts = new RestEndpoint({
path: '/:group/posts',
searchParams: {} as { orderBy?: string; author?: string },
schema: new schema.Collection([Post], {
nonFilterArgumentKeys: ['orderBy'],
}),
});

In this case, author and group are considered 'filter' argument keys, which means they will influence whether a newly created should be added to those lists. On the other hand, orderBy does not need to match when push is called.

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

class Post extends Entity {
  id = '';
  title = '';
  group = '';
  author = '';
}
export const getPosts = new RestEndpoint({
  path: '/:group/posts',
  searchParams: {} as { orderBy?: string; author?: string },
  schema: new schema.Query(
    new schema.Collection([Post], {
      nonFilterArgumentKeys: /orderBy/,
    }),
    (posts, { orderBy } = {}) => {
      if (orderBy) {
        return [...posts].sort((a, b) => a[orderBy].localeCompare(b[orderBy]));
      }
      return posts;
    },
  )
});
🔴 Live Preview
Store

createCollectionFilter?

Sets a default createCollectionFilter for addWith(), push, unshift, and assign.

This is used by these creation schemas to determine which collections to add to.

Default:

createCollectionFilter(...args: Args) {
return (collectionKey: Record<string, string>) =>
Object.entries(collectionKey).every(
([key, value]) =>
this.nonFilterArgumentKeys(key) ||
// strings are canonical form. See pk() above for value transformation
`${args[0][key]}` === value ||
`${args[1]?.[key]}` === value,
);
}

Methods

push

A creation schema that places at the end of this collection

unshift

A creation schema that places at the start of this collection

assign

A creation schema that assigns its members to the Collection.

addWith(merge, createCollectionFilter): CreationSchema

Constructs a custom creation schema for this collection. This is used by push, unshift, assign and paginate

merge(collection, creation)

This merges the value with the existing collection

createCollectionFilter

This function is used to determine which collections to add to. It uses the Object returned from argsKey or nestKey to determine if that collection should get the newly created values from this schema.

Because arguments may be serializable types like number, we recommend using == comparisons, e.g., '10' == 10

(...args) =>
collectionKey =>
boolean;

Lifecycle Methods

static shouldReorder(existingMeta, incomingMeta, existing, incoming): boolean

static shouldReorder(
existingMeta: { date: number; fetchedAt: number },
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return incomingMeta.fetchedAt < existingMeta.fetchedAt;
}

true return value will reorder incoming vs in-store entity argument order in merge. With the default merge, this will cause the fields of existing entities to override those of incoming, rather than the other way around.

static merge(existing, incoming): mergedValue

static merge(existing: any, incoming: any) {
return incoming;
}

static mergeWithStore(existingMeta, incomingMeta, existing, incoming): mergedValue

static mergeWithStore(
existingMeta: { date: number; fetchedAt: number },
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
): any;

mergeWithStore() is called during normalization when a processed entity is already found in the store.

pk: (parent?, key?, args?): pk?

pk() calls argsKey or nestKey depending on which are specified, and then serializes the result for the pk string.

pk(value: any, parent: any, key: string, args: readonly any[]) {
const obj = this.argsKey
? this.argsKey(...args)
: this.nestKey(parent, key);
for (const key in obj) {
if (typeof obj[key] !== 'string') obj[key] = `${obj[key]}`;
}
return JSON.stringify(obj);
}