TinyBase

API

store

The store module is the core of the TinyBase project and contains the types, interfaces, and functions to work with Store objects.

The main entry point to this module is the createStore function, which returns a new Store. From there, you can set and get data, register listeners, and use other modules to build an entire app around the state and tabular data within.

Interfaces

There is one interface, Store, within the store module.

Store

A Store is the main location for keeping structured state and tabular data.

Create a Store easily with the createStore function. From there, you can set and get data, add listeners for when the data changes, set a Schema, and so on.

A Store has a simple hierarchical structure:

  • The Store contains a number of Table objects.
  • Each Table contains a number of Row objects.
  • Each Row contains a number of Cell objects.

A Cell is a string, boolean, or number value.

The members of each level of this hierarchy are identified with a unique Id (which is a string). In other words you can naively think of a Store as a three-level-deep JavaScript object, keyed with strings:

{                     // Store
  "table1": {           // Table
    "row1": {             // Row
      "cell1": "one",       // Cell (string)
      "cell2": true,        // Cell (boolean)
      "cell3": 3,           // Cell (number)
      ...
    },
    ...
  },
  ...
}

In its default form, a Store has no sense of a structured schema, so, as long as they are unique within their own parent, the Id keys can each be any string you want. However, you can optionally specify a Schema for a Store, which then usefully constrains the Table and Cell Ids (and Cell values) you can use.

Setting and getting data

Every part of the Store can be accessed with getter methods. When you retrieve data from the Store, you are receiving a copy - rather than a reference - of it. This means that manipulating the data in the Store must be performed with the equivalent setter and deleter methods.

To benefit from the reactive behavior of the Store, you can also subscribe to changes on any part of it with 'listeners'. Registering a listener returns a listener Id (that you can use later to remove it with the delListener method), and it will then be called every time there is a change within the part of the hierarchy you're listening to.

This table shows the main ways you can set, get, and listen to, different types of data in a Store:

Additionally, there are two extra methods to manipulate Row objects. The addRow method is like the setRow method but automatically assigns it a new unique Id. And the setPartialRow method lets you update multiple Cell values in a Row without affecting the others.

You can listen to attempts to write invalid data to a Cell with the addInvalidCellListener method.

The transaction method is used to wrap multiple changes to the Store so that the relevant listeners only fire once.

The setJson method and the getJson method allow you to work with a JSON-encoded representation of the entire Store, which is useful for persisting it.

Finally, the callListener method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed. This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store in bulk.

Read more about setting and changing data in The Basics guides, and about listeners in the Listening to Stores guide.

Creating a Schema

You can set a Schema on a Store when you create it with createStore function, or at a later stage with the setSchema method. A Schema constrains the Table Ids the Store can have, and the types of Cell data in each Table. Each Cell requires its type to be specified, and can also take a default value for when it's not specified.

You can also get a serialization of the Schema out of the Store with the getSchemaJson method, and remove the Schema altogether with the delSchema method.

Read more about schemas in the Using Schemas guide.

Convenience methods

There are a few additional helper methods to make it easier to work with a Store. There are methods for easily checking the existence of a Table, Row, or Cell, and iterators that let you act on the children of a common parent:

Finally, the getListenerStats method describes the current state of the Store's listeners for debugging purposes.

Example

This example shows a very simple lifecycle of a Store: from creation, to adding and getting some data, and then registering and removing a listener.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}

store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getCell('pets', 'fido', 'color'));
// -> 'brown'

const listenerId = store.addTableListener('pets', () => {
  console.log('changed');
});

store.setCell('pets', 'fido', 'sold', false);
// -> 'changed'

store.delListener(listenerId);
See also

The Basics guides

Using Schemas guides

Hello World demos

Todo App demos

Getter methods

This is the collection of getter methods within the Store interface. There are 13 getter methods in total.

getTables

The getTables method returns a Tables object containing the entire data of the Store.

getTables(): Tables
returnsTables

A Tables object containing the entire data of the Store.

Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.

Examples

This example retrieves the data in a Store.

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}},
  species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}

This example retrieves the Tables of an empty Store, returning an empty object.

const store = createStore();
console.log(store.getTables());
// -> {}
See also
Guides

Creating a Store

Indexes

hasTables

The hasTables method returns a boolean indicating whether any Table objects exist in the Store.

hasTables(): boolean
returnsboolean

Whether any Tables exist.

Example

This example shows simple existence checks.

const store = createStore();
console.log(store.hasTables());
// -> false
store.setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTables());
// -> true
getTableIds

The getTableIds method returns the Ids of every Table in the Store.

getTableIds(): Ids
returnsIds

An array of the Ids of every Table in the Store.

Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself. Although the order of Ids have no meaning, this method is expected to return them in the order in which each Table was added.

Examples

This example retrieves the Table Ids in a Store.

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}},
  species: {dog: {price: 5}},
});
console.log(store.getTableIds());
// -> ['pets', 'species']

This example retrieves the Table Ids of an empty Store, returning an empty array.

const store = createStore();
console.log(store.getTableIds());
// -> []
getTable

The getTable method returns an object containing the entire data of a single Table in the Store.

getTable(tableId: string): Table
TypeDescription
tableIdstring

The Id of the Table in the Store.

returnsTable

An object containing the entire data of the Table.

Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.

Examples

This example retrieves the data in a single Table.

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}},
  species: {dog: {price: 5}},
});
console.log(store.getTable('pets'));
// -> {fido: {species: 'dog'}}

This example retrieves a Table that does not exist, returning an empty object.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTable('employees'));
// -> {}
hasTable

The hasTable method returns a boolean indicating whether a given Table exists in the Store.

hasTable(tableId: string): boolean
TypeDescription
tableIdstring

The Id of a possible Table in the Store.

returnsboolean

Whether a Table with that Id exists.

Example

This example shows two simple Table existence checks.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasTable('pets'));
// -> true
console.log(store.hasTable('employees'));
// -> false
getRowIds

The getRowIds method returns the Ids of every Row in a given Table.

getRowIds(tableId: string): Ids
TypeDescription
tableIdstring

The Id of the Table in the Store.

returnsIds

An array of the Ids of every Row in the Table.

Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself. Although the order of Ids have no meaning, this method is expected to return them in the order in which each Row was added.

Examples

This example retrieves the Row Ids in a Table.

const store = createStore().setTables({
  pets: {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
  },
});
console.log(store.getRowIds('pets'));
// -> ['fido', 'felix']

This example retrieves the Row Ids of a Table that does not exist, returning an empty array.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRowIds('employees'));
// -> []
getRow

The getRow method returns an object containing the entire data of a single Row in a given Table.

getRow(
  tableId: string,
  rowId: string,
): Row
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

returnsRow

An object containing the entire data of the Row.

Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.

Examples

This example retrieves the data in a single Row.

const store = createStore().setTables({
  pets: {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
  },
});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog'}

This example retrieves a Row that does not exist, returning an empty object.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getRow('pets', 'felix'));
// -> {}
hasRow

The hasRow method returns a boolean indicating whether a given Row exists in the Store.

hasRow(
  tableId: string,
  rowId: string,
): boolean
TypeDescription
tableIdstring

The Id of a possible Table in the Store.

rowIdstring

The Id of a possible Row in the Table.

returnsboolean

Whether a Row with that Id exists in that Table.

Example

This example shows two simple Row existence checks.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasRow('pets', 'fido'));
// -> true
console.log(store.hasRow('pets', 'felix'));
// -> false
getCellIds

The getCellIds method returns the Ids of every Cell in a given Row, in a given Table.

getCellIds(
  tableId: string,
  rowId: string,
): Ids
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

returnsIds

An array of the Ids of every Cell in the Row.

Note that this returns a copy of, rather than a reference, to the list of Ids, so changes made to the list are not made to the Store itself. Although the order of Ids have no meaning, this method is expected to return them in the order in which each Row was added.

Examples

This example retrieves the Cell Ids in a Row.

const store = createStore().setTables({
  pets: {
    fido: {species: 'dog', color: 'brown'},
  },
});
console.log(store.getCellIds('pets', 'fido'));
// -> ['species', 'color']

This example retrieves the Cell Ids of a Cell that does not exist, returning an empty array.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCellIds('pets', 'felix'));
// -> []
getCell

The getCell method returns an object containing the value of a single Cell in a given Row, in a given Table.

getCell(
  tableId: string,
  rowId: string,
  cellId: string,
): CellOrUndefined
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

cellIdstring

The Id of the Cell in the Row.

returnsCellOrUndefined

The value of the Cell.

Note that this returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the Store itself.

Examples

This example retrieves a single Cell.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
console.log(store.getCell('pets', 'fido', 'species'));
// -> 'dog'

This example retrieves a Cell that does not exist, returning undefined.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getCell('pets', 'fido', 'color'));
// -> undefined
hasCell

The hasCell method returns a boolean indicating whether a given Cell exists in the Store.

hasCell(
  tableId: string,
  rowId: string,
  cellId: string,
): boolean
TypeDescription
tableIdstring

The Id of a possible Table in the Store.

rowIdstring

The Id of a possible Row in the Table.

cellIdstring

The Id of a possible Cell in the Row.

returnsboolean

Whether a Cell with that Id exists in that Row in that Table.

Example

This example shows two simple Cell existence checks.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.hasCell('pets', 'fido', 'species'));
// -> true
console.log(store.hasCell('pets', 'fido', 'color'));
// -> false
getJson

The getJson method returns a string serialization of all of the Tables in the Store.

getJson(): string
returnsstring

A string serialization of all of the Tables in the Store.

Examples

This example serializes the contents of a Store.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getJson());
// -> '{"pets":{"fido":{"species":"dog"}}}'

This example serializes the contents of an empty Store.

const store = createStore();
console.log(store.getJson());
// -> '{}'
getSchemaJson

The getSchemaJson method returns a string serialization of the Schema of the Store.

getSchemaJson(): string
returnsstring

A string serialization of the Schema of the Store.

If no Schema has been set on the Store (or if it has been removed with the delSchema method), then it will return the serialization of an empty object, {}.

Examples

This example serializes the Schema of a Store.

const store = createStore().setSchema({
  pets: {
    species: {type: 'string'},
    sold: {type: 'boolean'},
  },
});
console.log(store.getSchemaJson());
// -> '{"pets":{"species":{"type":"string"},"sold":{"type":"boolean"}}}'

This example serializes the Schema of an empty Store.

const store = createStore();
console.log(store.getSchemaJson());
// -> '{}'
Setter methods

This is the collection of setter methods within the Store interface. There are 8 setter methods in total.

setTables

The setTables method takes an object and sets the entire data of the Store.

setTables(tables: Tables): Store
TypeDescription
tablesTables

The data of the Store to be set.

returnsStore

This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.

Any part of the provided object that is invalid (either according to the Tables type, or because it does not match a Schema associated with the Store), will be ignored silently.

Assuming that at least some of the provided Tables object is valid, any data that was already present in the Store will be completely overwritten. If the object is completely invalid, no change will be made to the Store.

The method returns a reference to the Store to that subsequent operations can be chained in a fluent style.

Examples

This example sets the data of a Store.

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}},
  species: {dog: {price: 5}},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}, species: {dog: {price: 5}}}

This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Tables objects.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});

store.setTables({pets: {felix: {species: 'cat', bug: []}}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}

store.setTables({meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
setTable

The setTable method takes an object and sets the entire data of a single Table in the Store.

setTable(
  tableId: string,
  table: Table,
): Store
TypeDescription
tableIdstring

The Id of the Table in the Store.

tableTable

The data of a single Table to be set.

returnsStore

This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.

Any part of the provided object that is invalid (either according to the Table type, or because it does not match a Schema associated with the Store), will be ignored silently.

Assuming that at least some of the provided Table object is valid, any data that was already present in the Store for that Table will be completely overwritten. If the object is completely invalid, no change will be made to the Store.

The method returns a reference to the Store to that subsequent operations can be chained in a fluent style.

Examples

This example sets the data of a single Table.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}, felix: {species: 'cat'}}}

This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Table objects.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});

store.setTable('pets', {felix: {species: 'cat', bug: []}});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}

store.setTable('pets', {meaning: 42});
console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
setRow

The setRow method takes an object and sets the entire data of a single Row in the Store.

setRow(
  tableId: string,
  rowId: string,
  row: Row,
): Store
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

rowRow

The data of a single Row to be set.

returnsStore

A reference to the Store.

This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.

Any part of the provided object that is invalid (either according to the Row type, or because it does not match a Schema associated with the Store), will be ignored silently.

Assuming that at least some of the provided Row object is valid, any data that was already present in the Store for that Row will be completely overwritten. If the object is completely invalid, no change will be made to the Store.

The method returns a reference to the Store to that subsequent operations can be chained in a fluent style.

Examples

This example sets the data of a single Row.

const store = createStore().setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}

This example attempts to set the data of an existing Store with partly invalid, and then completely invalid, Row objects.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});

store.setRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}

store.setRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {color: 'brown'}}}
addRow

The addRow method takes an object and creates a new Row in the Store, returning the unique Id assigned to it.

addRow(
  tableId: string,
  row: Row,
): undefined | string
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowRow

The data of a single Row to be added.

returnsundefined | string

A reference to the Store.

This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.

Any part of the provided object that is invalid (either according to the Row type, or because it does not match a Schema associated with the Store), will be ignored silently.

Assuming that at least some of the provided Row object is valid, a new Row will be created. If the object is completely invalid, no change will be made to the Store and the method will return undefined

You should not guarantee the form of the unique Id that is generated when a Row is added to the Table. However it is likely to be a string representation of an increasing integer.

Examples

This example adds a single Row.

const store = createStore();
console.log(store.addRow('pets', {species: 'dog'}));
// -> '0'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}}}

This example attempts to add Rows to an existing Store with partly invalid, and then completely invalid, Row objects.

const store = createStore().setTables({pets: {'0': {species: 'dog'}}});

console.log(store.addRow('pets', {species: 'cat', bug: []}));
// -> '1'
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}

console.log(store.addRow('pets', 42));
// -> undefined
console.log(store.getTables());
// -> {pets: {'0': {species: 'dog'}, '1': {species: 'cat'}}}
setPartialRow

The setPartialRow method takes an object and sets partial data of a single Row in the Store, leaving other Cell values unaffected.

setPartialRow(
  tableId: string,
  rowId: string,
  partialRow: Row,
): Store
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

partialRowRow

The partial data of a single Row to be set.

returnsStore

A reference to the Store.

This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.

Any part of the provided object that is invalid (either according to the Row type, or because, when combined with the current Row data, it does not match a Schema associated with the Store), will be ignored silently.

Assuming that at least some of the provided Row object is valid, it will be merged with the data that was already present in the Store. If the object is completely invalid, no change will be made to the Store.

The method returns a reference to the Store to that subsequent operations can be chained in a fluent style.

Examples

This example sets some of the data of a single Row.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
store.setPartialRow('pets', 'fido', {color: 'walnut', visits: 1});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'walnut', visits: 1}}}

This example attempts to set some of the data of an existing Store with partly invalid, and then completely invalid, Row objects.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});

store.setPartialRow('pets', 'fido', {color: 'brown', bug: []});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}

store.setPartialRow('pets', 'fido', 42);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
setCell

The setCell method sets the value of a single Cell in the Store.

setCell(
  tableId: string,
  rowId: string,
  cellId: string,
  cell: Cell | MapCell,
): Store
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

cellIdstring

The Id of the Cell in the Row.

cellCell | MapCell

The value of the Cell to be set, or a MapCell function to update it.

returnsStore

A reference to the Store.

This method will cause listeners to be called for any Table, Row, Cell, or Id changes resulting from it.

If the Cell value is invalid (either because of its type, or because it does not match a Schema associated with the Store), will be ignored silently.

As well as string, number, or boolean Cell types, this method can also take a MapCell function that takes the current Cell value as a parameter and maps it. This is useful if you want to efficiently increment a value without fetching it first, for example.

The method returns a reference to the Store to that subsequent operations can be chained in a fluent style.

Examples

This example sets the value of a single Cell.

const store = createStore().setCell('pets', 'fido', 'species', 'dog');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}

This example sets the data of a single Cell by mapping the existing value.

const increment = (cell) => cell + 1;
const store = createStore().setTables({pets: {fido: {visits: 1}}});

store.setCell('pets', 'fido', 'visits', increment);
console.log(store.getCell('pets', 'fido', 'visits'));
// -> 2

This example attempts to set the data of an existing Store with an invalid Cell value.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});

store.setCell('pets', 'fido', 'bug', []);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}
setJson

The setJson method takes a string serialization of all of the Tables in the Store and attempts to update it to that value

setJson(json: string): Store
TypeDescription
jsonstring

A string serialization of all of the Tables in the Store.

returnsStore

A reference to the Store.

If the JSON cannot be parsed, this will fail silently. If it can be parsed, it will then be subject to the same validation rules as the setTables method (according to the Tables type, and matching any Schema associated with the Store).

Examples

This example sets the contents of a Store from a serialization.

const store = createStore();
store.setJson('{"pets":{"fido":{"species":"dog"}}}');
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}

This example attempts to set the contents of a Store from an invalid serialization.

const store = createStore();
store.setJson('{"pets":{"fido":{');
console.log(store.getTables());
// -> {}
setSchema

The setSchema method lets you specify the Schema of the Store.

setSchema(tablesSchema: Schema): Store
TypeDescription
tablesSchemaSchema
returnsStore

A reference to the Store.

Note that this may result in a change to data in the Store, as defaults are applied or as invalid Table, Row, or Cell objects are removed. These changes will fire any listeners to that data, as expected.

When no longer needed, you can also completely remove an existing Schema with the delSchema method.

Example

This example sets the Schema of a Store after it has been created.

const store = createStore().setSchema({
  pets: {
    species: {type: 'string'},
    sold: {type: 'boolean', default: false},
  },
});
store.addRow('pets', {species: 'dog', color: 'brown', sold: 'maybe'});
console.log(store.getTables());
// -> {pets: {0: {species: 'dog', sold: false}}}
Listener methods

This is the collection of listener methods within the Store interface. There are 10 listener methods in total.

addTablesListener

The addTablesListener method registers a listener function with the Store that will be called whenever data in the Store changes.

addTablesListener(
  listener: TablesListener,
  mutator?: boolean,
): string
TypeDescription
listenerTablesListener

The function that will be called whenever data in the Store changes.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

returnsstring

A unique Id for the listener that can later be used to call it explicitly, or to remove it.

The provided listener is a TablesListener function, and will be called with a reference to the Store and a GetCellChange function in case you need to inspect any changes that occurred.

Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.

Examples

This example registers a listener that responds to any changes to the whole Store.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener((store, getCellChange) => {
  console.log('Tables changed');
  console.log(getCellChange('pets', 'fido', 'color'));
});

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'
// -> [true, 'brown', 'walnut']

store.delListener(listenerId);

This example registers a listener that responds to any changes to the whole Store, and which also mutates the Store itself.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(
  (store) => store.setCell('meta', 'update', 'store', true),
  true,
);

store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {store: true}}

store.delListener(listenerId);
addTableIdsListener

The addTableIdsListener method registers a listener function with the Store that will be called whenever the Table Ids in the Store change.

addTableIdsListener(
  listener: TableIdsListener,
  mutator?: boolean,
): string
TypeDescription
listenerTableIdsListener

The function that will be called whenever the Table Ids in the Store change.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

returnsstring

A unique Id for the listener that can later be used to call it explicitly, or to remove it.

Such a listener is only called when a Table is added or removed. To listen to all changes in the Store, use the addTablesListener method.

The provided listener is a TableIdsListener function, and will be called with a reference to the Store.

Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.

Examples

This example registers a listener that responds to any change to the Table Ids.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener((store) => {
  console.log('Table Ids changed');
  console.log(store.getTableIds());
});

store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'
// -> ['pets', 'species']

store.delListener(listenerId);

This example registers a listener that responds to any change to the Table Ids, and which also mutates the Store itself.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addTableIdsListener(
  (store) => store.setCell('meta', 'update', 'store', true),
  true,
);

store.setTable('species', {dog: {price: 5}});
console.log(store.getTable('meta'));
// -> {update: {store: true}}

store.delListener(listenerId);
addTableListener

The addTableListener method registers a listener function with the Store that will be called whenever data in a Table changes.

addTableListener(
  tableId: IdOrNull,
  listener: TableListener,
  mutator?: boolean,
): string
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

listenerTableListener

The function that will be called whenever data in the matching Table changes.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

returnsstring

A unique Id for the listener that can later be used to call it explicitly, or to remove it.

You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).

The provided listener is a TableListener function, and will be called with a reference to the Store, the Id of the Table that changed, and a GetCellChange function in case you need to inspect any changes that occurred.

Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.

Examples

This example registers a listener that responds to any changes to a specific Table.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
  'pets',
  (store, tableId, getCellChange) => {
    console.log('pets table changed');
    console.log(getCellChange('pets', 'fido', 'color'));
  },
);

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
// -> [true, 'brown', 'walnut']

store.delListener(listenerId);

This example registers a listener that responds to any changes to any Table.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(null, (store, tableId) => {
  console.log(`${tableId} table changed`);
});

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'species table changed'

store.delListener(listenerId);

This example registers a listener that responds to any changes to a specific Table, and which also mutates the Store itself.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTableListener(
  'pets',
  (store, tableId) => store.setCell('meta', 'update', tableId, true),
  true,
);

store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets: true}}

store.delListener(listenerId);
addRowIdsListener

The addRowIdsListener method registers a listener function with the Store that will be called whenever the Row Ids in a Table change.

addRowIdsListener(
  tableId: IdOrNull,
  listener: RowIdsListener,
  mutator?: boolean,
): string
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

listenerRowIdsListener

The function that will be called whenever the Row Ids in the Table change.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

returnsstring

A unique Id for the listener that can later be used to call it explicitly, or to remove it.

Such a listener is only called when a Row is added or removed. To listen to all changes in the Table, use the addTableListener method.

You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing null).

The provided listener is a RowIdsListener function, and will be called with a reference to the Store and the Id of the Table that changed.

Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.

Examples

This example registers a listener that responds to any change to the Row Ids of a specific Table.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener('pets', (store) => {
  console.log('Row Ids for pets table changed');
  console.log(store.getRowIds('pets'));
});

store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']

store.delListener(listenerId);

This example registers a listener that responds to any change to the Row Ids of any Table.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(null, (store, tableId) => {
  console.log(`Row Ids for ${tableId} table changed`);
  console.log(store.getRowIds(tableId));
});

store.setRow('pets', 'felix', {species: 'cat'});
// -> 'Row Ids for pets table changed'
// -> ['fido', 'felix']
store.setRow('species', 'dog', {price: 5});
// -> 'Row Ids for species table changed'
// -> ['dog']

store.delListener(listenerId);

This example registers a listener that responds to any change to the Row Ids of a specific Table, and which also mutates the Store itself.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addRowIdsListener(
  'pets',
  (store, tableId) => store.setCell('meta', 'update', tableId, true),
  true,
);

store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}

store.delListener(listenerId);
addRowListener

The addRowListener method registers a listener function with the Store that will be called whenever data in a Row changes.

addRowListener(
  tableId: IdOrNull,
  rowId: IdOrNull,
  listener: RowListener,
  mutator?: boolean,
): string
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

rowIdIdOrNull

The Id of the Row to listen to, or null as a wildcard.

listenerRowListener

The function that will be called whenever data in the matching Row changes.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

returnsstring

A unique Id for the listener that can later be used to call it explicitly, or to remove it.

You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).

Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.

The provided listener is a RowListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and a GetCellChange function in case you need to inspect any changes that occurred.

Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.

Examples

This example registers a listener that responds to any changes to a specific Row.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
  'pets',
  'fido',
  (store, tableId, rowId, getCellChange) => {
    console.log('fido row in pets table changed');
    console.log(getCellChange('pets', 'fido', 'color'));
  },
);

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
// -> [true, 'brown', 'walnut']

store.delListener(listenerId);

This example registers a listener that responds to any changes to any Row.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
  null,
  null,
  (store, tableId, rowId) => {
    console.log(`${rowId} row in ${tableId} table changed`);
  },
);

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'dog row in species table changed'

store.delListener(listenerId);

This example registers a listener that responds to any changes to a specific Row, and which also mutates the Store itself.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addRowListener(
  'pets',
  'fido',
  (store, tableId, rowId) =>
    store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
  true,
);

store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}

store.delListener(listenerId);
addCellIdsListener

The addCellIdsListener method registers a listener function with the Store that will be called whenever the Cell Ids in a Row change.

addCellIdsListener(
  tableId: IdOrNull,
  rowId: IdOrNull,
  listener: CellIdsListener,
  mutator?: boolean,
): string
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

rowIdIdOrNull

The Id of the Row to listen to, or null as a wildcard.

listenerCellIdsListener

The function that will be called whenever the Cell Ids in the Row change.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

returnsstring

A unique Id for the listener that can later be used to call it explicitly, or to remove it.

Such a listener is only called when a Cell is added or removed. To listen to all changes in the Row, use the addRowListener method.

You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null).

Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.

The provided listener is a CellIdsListener function, and will be called with a reference to the Store, the Id of the Table, and the Id of the Row that changed.

Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.

Examples

This example registers a listener that responds to any change to the Cell Ids of a specific Row.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener('pets', 'fido', (store) => {
  console.log('Cell Ids for fido row in pets table changed');
  console.log(store.getCellIds('pets', 'fido'));
});

store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']

store.delListener(listenerId);

This example registers a listener that responds to any change to the Cell Ids of any Row.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
  null,
  null,
  (store, tableId, rowId) => {
    console.log(`Cell Ids for ${rowId} row in ${tableId} table changed`);
    console.log(store.getCellIds(tableId, rowId));
  },
);

store.setCell('pets', 'fido', 'color', 'brown');
// -> 'Cell Ids for fido row in pets table changed'
// -> ['species', 'color']
store.setCell('species', 'dog', 'price', 5);
// -> 'Cell Ids for dog row in species table changed'
// -> ['price']

store.delListener(listenerId);

This example registers a listener that responds to any change to the Cell Ids of a specific Row, and which also mutates the Store itself.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const listenerId = store.addCellIdsListener(
  'pets',
  'fido',
  (store, tableId, rowId) =>
    store.setCell('meta', 'update', `${tableId}_${rowId}`, true),
  true,
);

store.setCell('pets', 'fido', 'color', 'brown');
console.log(store.getTable('meta'));
// -> {update: {pets_fido: true}}

store.delListener(listenerId);
addCellListener

The addCellListener method registers a listener function with the Store that will be called whenever data in a Cell changes.

addCellListener(
  tableId: IdOrNull,
  rowId: IdOrNull,
  cellId: IdOrNull,
  listener: CellListener,
  mutator?: boolean,
): string
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

rowIdIdOrNull

The Id of the Row to listen to, or null as a wildcard.

cellIdIdOrNull

The Id of the Cell to listen to, or null as a wildcard.

listenerCellListener

The function that will be called whenever data in the matching Cell changes.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

returnsstring

A unique Id for the listener that can later be used to call it explicitly, or to remove it.

You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).

All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.

The provided listener is a CellListener function, and will be called with a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, the Id of the Cell that changed, the new Cell value, the old Cell value, and a GetCellChange function in case you need to inspect any changes that occurred.

Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.

Examples

This example registers a listener that responds to any changes to a specific Cell.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
  'pets',
  'fido',
  'color',
  (store, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
    console.log('color cell in fido row in pets table changed');
    console.log([oldCell, newCell]);
    console.log(getCellChange('pets', 'fido', 'color'));
  },
);

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']

store.delListener(listenerId);

This example registers a listener that responds to any changes to any Cell.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
  null,
  null,
  null,
  (store, tableId, rowId, cellId) => {
    console.log(
      `${cellId} cell in ${rowId} row in ${tableId} table changed`,
    );
  },
);

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table changed'

store.delListener(listenerId);

This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
  'pets',
  'fido',
  'color',
  (store, tableId, rowId, cellId) =>
    store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
  true,
);

store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}

store.delListener(listenerId);
addInvalidCellListener

The addInvalidCellListener method registers a listener function with the Store that will be called whenever invalid data was attempted to be written to a Cell.

addInvalidCellListener(
  tableId: IdOrNull,
  rowId: IdOrNull,
  cellId: IdOrNull,
  listener: InvalidCellListener,
  mutator?: boolean,
): string
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

rowIdIdOrNull

The Id of the Row to listen to, or null as a wildcard.

cellIdIdOrNull

The Id of the Cell to listen to, or null as a wildcard.

listenerInvalidCellListener

The function that will be called whenever an attempt to write invalid data to the matching Cell was made.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

returnsstring

A unique Id for the listener that can later be used to call it explicitly, or to remove it.

You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or invalid attempts to change any Cell (by providing null wildcards).

All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.

The provided listener is an InvalidCellListener function, and will be called with a reference to the Store, the Id of the Table, the Id of the Row, and the Id of Cell that were being attempted to be changed. It is also given the invalid value of the Cell, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell within a single transaction, this is an array containing each attempt, chronologically.

Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store data. If set to false (or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.

Special note should be made for how the listener will be called when a Schema is present. The listener will be called:

  • if a Table is being updated that is not specified in the Schema
  • if a Cell is of the wrong type specified in the Schema
  • if a Cell is omitted and is not defaulted in the Schema
  • if an empty Row is provided and there are no Cell defaults in the Schema

The listener will not be called if Cell that is defaulted in the Schema is not provided, as long as all of the Cells that are not defaulted are provided.

To help understand all of these schema-based conditions, please see the Schema example below.

Examples

This example registers a listener that responds to any invalid changes to a specific Cell.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
  'pets',
  'fido',
  'color',
  (store, tableId, rowId, cellId, invalidCells) => {
    console.log('Invalid color cell in fido row in pets table');
    console.log(invalidCells);
  },
);

store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
// -> [{r: '96', g: '4B', b: '00'}]

store.delListener(listenerId);

This example registers a listener that responds to any invalid changes to any Cell - in a Store without a Schema. Note also how it then responds to cases where an empty or invalid Row objects, or Table objects, or Tables objects are provided.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
  null,
  null,
  null,
  (store, tableId, rowId, cellId) => {
    console.log(
      `Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
    );
  },
);

store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
store.setTable('sales', {fido: {date: new Date()}});
// -> 'Invalid date cell in fido row in sales table'

store.setRow('pets', 'felix', {});
// -> 'Invalid undefined cell in felix row in pets table'

store.setRow('filter', 'name', /[a-z]?/);
// -> 'Invalid undefined cell in name row in filter table'

store.setRow('sales', '2021', {forecast: undefined});
// -> 'Invalid forecast cell in 2021 row in sales table'

store.addRow('filter', /[0-9]?/);
// -> 'Invalid undefined cell in undefined row in filter table'

store.setTable('raw', {});
// -> 'Invalid undefined cell in undefined row in raw table'

store.setTable('raw', ['row1', 'row2']);
// -> 'Invalid undefined cell in undefined row in raw table'

store.setTables(['table1', 'table2']);
// -> 'Invalid undefined cell in undefined row in undefined table'

store.delListener(listenerId);

This example registers a listener that responds to any invalid changes to any Cell - in a Store with a Schema. Note how it responds to cases where missing parameters are provided for optional, and defaulted Cell values in a Row.

const store = createStore().setSchema({
  pets: {
    species: {type: 'string'},
    color: {type: 'string', default: 'unknown'},
  },
});

const listenerId = store.addInvalidCellListener(
  null,
  null,
  null,
  (store, tableId, rowId, cellId) => {
    console.log(
      `Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
    );
  },
);

store.setRow('sales', 'fido', {price: 5});
// -> 'Invalid price cell in fido row in sales table'
// The listener is called, because the sales Table is not in the schema

store.setRow('pets', 'felix', {species: true});
// -> 'Invalid species cell in felix row in pets table'
// The listener is called, because species is invalid...
console.log(store.getRow('pets', 'felix'));
// -> {color: 'unknown'}
// ...even though a Row was set with the default value

store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Invalid species cell in fido row in pets table'
// The listener is called, because species is missing and not defaulted...
console.log(store.getRow('pets', 'fido'));
// -> {color: 'brown'}
// ...even though a Row was set

store.setRow('pets', 'rex', {species: 'dog'});
console.log(store.getRow('pets', 'rex'));
// -> {species: 'dog', color: 'unknown'}
// The listener is not called, because color is defaulted

store.delTables().setSchema({
  pets: {
    species: {type: 'string'},
    color: {type: 'string'},
  },
});

store.setRow('pets', 'cujo', {});
// -> 'Invalid species cell in cujo row in pets table'
// -> 'Invalid color cell in cujo row in pets table'
// -> 'Invalid undefined cell in cujo row in pets table'
// The listener is called multiple times, because neither Cell is defaulted
// and the Row as a whole is empty

store.delListener(listenerId);

This example registers a listener that responds to any changes to a specific Cell, and which also mutates the Store itself.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
  'pets',
  'fido',
  'color',
  (store, tableId, rowId, cellId, invalidCells) =>
    store.setCell(
      'meta',
      'invalid_updates',
      `${tableId}_${rowId}_${cellId}`,
      JSON.stringify(invalidCells[0]),
    ),
  true,
);

store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
console.log(store.getRow('meta', 'invalid_updates'));
// -> {'pets_fido_color': '{"r":"96","g":"4B","b":"00"}'}

store.delListener(listenerId);
callListener

The callListener method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed.

callListener(listenerId: string): Store
TypeDescription
listenerIdstring

The Id of the listener to call.

returnsStore

A reference to the Store.

This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store in bulk.

Example

This example registers a listener that ensures a Cell has one of list of a valid values. After that list changes, the listener is called to apply the condition to the existing data.

const validColors = ['walnut', 'brown', 'black'];
const store = createStore();
const listenerId = store.addCellListener(
  'pets',
  null,
  'color',
  (store, tableId, rowId, cellId, color) => {
    if (!validColors.includes(color)) {
      store.setCell(tableId, rowId, cellId, validColors[0]);
    }
  },
  true,
);

store.setRow('pets', 'fido', {species: 'dog', color: 'honey'});
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'walnut'}

validColors.shift();
console.log(validColors);
// -> ['brown', 'black']

store.callListener(listenerId);
console.log(store.getRow('pets', 'fido'));
// -> {species: 'dog', color: 'brown'}

store.delListener(listenerId);
delListener

The delListener method removes a listener that was previously added to the Store.

delListener(listenerId: string): Store
TypeDescription
listenerIdstring

The Id of the listener to remove.

returnsStore

A reference to the Store.

Use the Id returned by whichever method was used to add the listener. Note that the Store may re-use this Id for future listeners added to it.

Example

This example registers a listener and then removes it.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addTablesListener(() => {
  console.log('Tables changed');
});

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'

store.delListener(listenerId);

store.setCell('pets', 'fido', 'color', 'honey');
// -> undefined
// The listener is not called.
Iterator methods

This is the collection of iterator methods within the Store interface. There are only three iterator methods, forEachTable, forEachRow, and forEachCell.

forEachTable

The forEachTable method takes a function that it will then call for each Table in the Store.

forEachTable(tableCallback: TableCallback): void
TypeDescription
tableCallbackTableCallback

The function that should be called for every Table.

returnsvoid

This has no return value.

This method is useful for iterating over the Table structure of the Store in a functional style. The tableCallback parameter is a TableCallback function that will be called with the Id of each Table, and with a function that can then be used to iterate over each Row of the Table, should you wish.

Example

This example iterates over each Table in a Store, and lists each Row Id within them.

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}},
  species: {dog: {price: 5}},
});
store.forEachTable((tableId, forEachRow) => {
  console.log(tableId);
  forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'pets'
// -> '- fido'
// -> 'species'
// -> '- dog'
forEachRow

The forEachRow method takes a function that it will then call for each Row in a specified Table.

forEachRow(
  tableId: string,
  rowCallback: RowCallback,
): void
TypeDescription
tableIdstring

The Id of the Table to iterate over.

rowCallbackRowCallback

The function that should be called for every Row.

returnsvoid

This has no return value.

This method is useful for iterating over the Row structure of the Table in a functional style. The rowCallback parameter is a RowCallback function that will be called with the Id of each Row, and with a function that can then be used to iterate over each Cell of the Row, should you wish.

Example

This example iterates over each Row in a Table, and lists each Cell Id within them.

const store = createStore().setTables({
  pets: {
    fido: {species: 'dog'},
    felix: {color: 'black'},
  },
});
store.forEachRow('pets', (rowId, forEachCell) => {
  console.log(rowId);
  forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- species'
// -> 'felix'
// -> '- color'
forEachCell

The forEachCell method takes a function that it will then call for each Cell in a specified Row.

forEachCell(
  tableId: string,
  rowId: string,
  cellCallback: CellCallback,
): void
TypeDescription
tableIdstring

The Id of the Table containing the Row to iterate over.

rowIdstring

The Id of the Row to iterate over.

cellCallbackCellCallback

The function that should be called for every Cell.

returnsvoid

This has no return value.

This method is useful for iterating over the Cell structure of the Row in a functional style. The cellCallback parameter is a CellCallback function that will be called with the Id and value of each Cell.

Example

This example iterates over each Cell in a Row, and lists its value.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});
store.forEachCell('pets', 'fido', (cellId, cell) => {
  console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Transaction methods

This is the collection of transaction methods within the Store interface. There is only one method, transaction.

transaction

The transaction method takes a function that makes multiple mutations to the store, buffering all calls to the relevant listeners until it completes.

transaction<Return>(
  actions: () => Return,
  doRollback?: (changedCells: ChangedCells, invalidCells: InvalidCells) => boolean,
): Return
TypeDescription
actions() => Return

The function to be executed as a transaction.

doRollback?(changedCells: ChangedCells, invalidCells: InvalidCells) => boolean

An optional callback that should return true if you want to rollback the transaction at the end.

returnsReturn

Whatever value the provided transaction function returns.

This method is useful for making bulk changes to the data in a Store, and when you don't want listeners to be called as you make each change. Changes are made silently during the transaction, and listeners relevant to the changes you have made will instead only be called when the whole transaction is complete.

If multiple changes are made to a piece of Store data throughout the transaction, a relevant listener will only be called with the final value (assuming it is different to the value at the start of the transaction), regardless of the changes that happened in between. For example, if a Cell had a value 'a' and then, within a transaction, it was changed to 'b' and then 'c', any CellListener registered for that cell would be called once as if there had been a single change from 'a' to 'c'.

Transactions can be nested. Relevant listeners will be called only when the outermost one completes.

The second, optional parameter, doRollback is a callback that you can use to rollback the transaction if it did not complete to your satisfaction. It is called with changedCells and invalidCells parameters, which inform you of the net changes that have been made during the transaction, and any invalid attempts to do so, respectively.

Examples

This example makes changes to two Cells, first outside, and secondly within, a transaction. In the second case, the Row listener is only called once.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));

store.setCell('pets', 'fido', 'color', 'brown');
store.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'

store.transaction(() => {
  store.setCell('pets', 'fido', 'color', 'walnut');
  store.setCell('pets', 'fido', 'sold', true);
});
// -> 'Fido changed'

This example makes multiple changes to one Cell. The Cell listener is called once - and with the final value - only if there is a net overall change.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addCellListener(
  'pets',
  'fido',
  'color',
  (store, tableId, rowId, cellId, newCell) => console.log(newCell),
);

store.transaction(() => {
  store.setCell('pets', 'fido', 'color', 'black');
  store.setCell('pets', 'fido', 'color', 'brown');
  store.setCell('pets', 'fido', 'color', 'walnut');
});
// -> 'walnut'

store.transaction(() => {
  store.setCell('pets', 'fido', 'color', 'black');
  store.setCell('pets', 'fido', 'color', 'walnut');
});
// -> undefined
// No net change during the transaction, so the listener is not called.

This example makes multiple changes to the Store, including some attempts to update a Cell with invalid values. The doRollback callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', color: 'brown'}},
});

store.transaction(
  () => {
    store.setCell('pets', 'fido', 'color', 'black');
    store.setCell('pets', 'fido', 'eyes', ['left', 'right']);
    store.setCell('pets', 'fido', 'info', {sold: null});
  },
  (changedCells, invalidCells) => {
    console.log(store.getTables());
    // -> {pets: {fido: {species: 'dog', color: 'black'}}}
    console.log(changedCells);
    // -> {pets: {fido: {color: ['brown', 'black']}}}
    console.log(invalidCells);
    // -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
    return invalidCells['pets'] != null;
  },
);

console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
Deleter methods

This is the collection of deleter methods within the Store interface. There are 5 deleter methods in total.

delTables

The delTables method lets you remove all of the data in a Store.

delTables(): Store
returnsStore

A reference to the Store.

Example

This example removes the data of a Store.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});

store.delTables();
console.log(store.getTables());
// -> {}
delTable

The delTable method lets you remove a single Table from the Store.

delTable(tableId: string): Store
TypeDescription
tableIdstring

The Id of the Table in the Store.

returnsStore

A reference to the Store.

Example

This example removes a Table from a Store.

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}},
  species: {dog: {price: 5}},
});
store.delTable('pets');

console.log(store.getTables());
// -> {species: {dog: {price: 5}}}
delRow

The delRow method lets you remove a single Row from a Table.

delRow(
  tableId: string,
  rowId: string,
): Store
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

returnsStore

A reference to the Store.

If this is the last Row in its Table, then that Table will be removed.

Example

This example removes a Row from a Table.

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}, felix: {species: 'cat'}},
});
store.delRow('pets', 'fido');

console.log(store.getTables());
// -> {pets: {felix: {species: 'cat'}}}
delCell

The delCell method lets you remove a single Cell from a Row.

delCell(
  tableId: string,
  rowId: string,
  cellId: string,
  forceDel?: boolean,
): Store
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

cellIdstring

The Id of the Cell in the Row.

forceDel?boolean

An optional flag to indicate that the whole Row should be deleted, even if a Schema provides a default value for this Cell. Defaults to false.

returnsStore

A reference to the Store.

When there is no Schema applied to the Store, then if this is the last Cell in its Row, then that Row will be removed. If, in turn, that is the last Row in its Table, then that Table will be removed.

If there is a Schema applied to the Store and it specifies a default value for this Cell, then deletion will result in it being set back to its default value. To override this, use the forceDel parameter, as described below.

The forceDel parameter is an optional flag that is only relevant if a Schema provides a default value for this Cell. Under such circumstances, deleting a Cell value will normally restore it to the default value. If this flag is set to true, the complete removal of the Cell is instead guaranteed. But since doing do so would result in an invalid Row (according to the Schema), in fact the whole Row is deleted to retain the integrity of the Table. Therefore, this flag should be used with caution.

Examples

This example removes a Cell from a Row without a Schema.

const store = createStore().setTables({
  pets: {fido: {species: 'dog', sold: true}},
});
store.delCell('pets', 'fido', 'sold');

console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}

This example removes a Cell from a Row with a Schema that defaults its value.

const store = createStore()
  .setTables({
    pets: {fido: {species: 'dog', sold: true}},
  })
  .setSchema({
    pets: {
      species: {type: 'string'},
      sold: {type: 'boolean', default: false},
    },
  });
store.delCell('pets', 'fido', 'sold');

console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}

This example removes a Cell from a Row with a Schema that defaults its value, but uses the forceDel parameter to override it.

const store = createStore()
  .setTables({
    pets: {fido: {species: 'dog', sold: true}, felix: {species: 'cat'}},
  })
  .setSchema({
    pets: {
      species: {type: 'string'},
      sold: {type: 'boolean', default: false},
    },
  });
store.delCell('pets', 'fido', 'sold', true);

console.log(store.getTables());
// -> {pets: {felix: {species: 'cat', sold: false}}}
delSchema

The delSchema method lets you remove the Schema of the Store.

delSchema(): Store
returnsStore

A reference to the Store.

Example

This example removes the Schema of a Store.

const store = createStore().setSchema({pets: {species: {type: 'string'}}});
store.delSchema();
console.log(store.getSchemaJson());
// -> '{}'
Development methods

This is the collection of development methods within the Store interface. There is only one method, getListenerStats.

getListenerStats

The getListenerStats method provides a set of statistics about the listeners registered with the Store, and is used for debugging purposes.

getListenerStats(): StoreListenerStats
returnsStoreListenerStats

A StoreListenerStats object containing Store listener statistics.

The StoreListenerStats object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners.

The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.

Example

This example gets the listener statistics of a small and simple Store.

const store = createStore();
store.addTablesListener(() => console.log('Tables changed'));
store.addRowIdsListener(() => console.log('Row Ids changed'));

const listenerStats = store.getListenerStats();
console.log(listenerStats.rowIds);
// -> 1
console.log(listenerStats.tables);
// -> 1

Functions

There is one function, createStore, within the store module.

createStore

The createStore function creates a Store, and is the main entry point into the store module.

createStore(): Store
returnsStore

A reference to the new Store.

Since (or perhaps because) it is the most important function in the whole module, it is trivially simple.

Examples

This example creates a Store.

const store = createStore();
console.log(store.getTables());
// -> {}

This example creates a Store with some initial data:

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}

This example creates a Store with some initial data and a Schema:

const store = createStore()
  .setTables({pets: {fido: {species: 'dog'}}})
  .setSchema({
    pets: {
      species: {type: 'string'},
      sold: {type: 'boolean', default: false},
    },
  });
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', sold: false}}}
See also

The Basics guides

Type aliases

These are the type aliases within the store module.

Listener type aliases

This is the collection of listener type aliases within the store module. There are 10 listener type aliases in total.

TablesListener

The TablesListener type describes a function that is used to listen to changes to the whole Store.

(
  store: Store,
  getCellChange: GetCellChange | undefined,
): void
TypeDescription
storeStore

A reference to the Store that changed.

getCellChangeGetCellChange | undefined

A function that returns information about any Cell's changes.

returnsvoid

This has no return value.

A TablesListener is provided when using the addTablesListener method. See that method for specific examples.

When called, a TablesListener is given a reference to the Store and a GetCellChange function that can be used to query Cell values before and after the current transaction.

Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.

TableIdsListener

The TableIdsListener type describes a function that is used to listen to changes to the Table Ids in the Store.

(store: Store): void
TypeDescription
storeStore

A reference to the Store that changed.

returnsvoid

This has no return value.

A TableIdsListener is provided when using the addTableIdsListener method. See that method for specific examples.

When called, a TableIdsListener is given a reference to the Store.

TableListener

The TableListener type describes a function that is used to listen to changes to a Table.

(
  store: Store,
  tableId: Id,
  getCellChange: GetCellChange | undefined,
): void
TypeDescription
storeStore

A reference to the Store that changed.

tableIdId

The Id of the Table that changed.

getCellChangeGetCellChange | undefined

A function that returns information about any Cell's changes.

returnsvoid

This has no return value.

A TableListener is provided when using the addTableListener method. See that method for specific examples.

When called, a TableListener is given a reference to the Store, the Id of the Table that changed, and a GetCellChange function that can be used to query Cell values before and after the current transaction.

Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.

RowIdsListener

The RowIdsListener type describes a function that is used to listen to changes to the Row Ids in a Table.

(
  store: Store,
  tableId: Id,
): void
TypeDescription
storeStore

A reference to the Store that changed.

tableIdId

The Id of the Table that changed.

returnsvoid

This has no return value.

A RowIdsListener is provided when using the addRowIdsListener method. See that method for specific examples.

When called, a RowIdsListener is given a reference to the Store, and the Id of the Table whose Row Ids changed.

RowListener

The RowListener type describes a function that is used to listen to changes to a Row.

(
  store: Store,
  tableId: Id,
  rowId: Id,
  getCellChange: GetCellChange | undefined,
): void
TypeDescription
storeStore

A reference to the Store that changed.

tableIdId

The Id of the Table that changed.

rowIdId

The Id of the Row that changed.

getCellChangeGetCellChange | undefined

A function that returns information about any Cell's changes.

returnsvoid

This has no return value.

A RowListener is provided when using the addRowListener method. See that method for specific examples.

When called, a RowListener is given a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and a GetCellChange function that can be used to query Cell values before and after the current transaction.

Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present.

CellIdsListener

The CellIdsListener type describes a function that is used to listen to changes to the Cell Ids in a Row.

(
  store: Store,
  tableId: Id,
  rowId: Id,
): void
TypeDescription
storeStore

A reference to the Store that changed.

tableIdId

The Id of the Table that changed.

rowIdId

The Id of the Row that changed. changes.

returnsvoid

This has no return value.

A CellIdsListener is provided when using the addCellIdsListener method. See that method for specific examples.

When called, a CellIdsListener is given a reference to the Store, the Id of the Table that changed, and the Id of the Row whose Cell Ids changed.

CellChange

The CellChange type describes a Cell's changes during a transaction.

[changed: boolean, oldCell: CellOrUndefined, newCell: CellOrUndefined]

This is returned by the GetCellChange function that is provided to every listener when called. This array contains the previous value of a Cell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.

CellListener

The CellListener type describes a function that is used to listen to changes to a Cell.

(
  store: Store,
  tableId: Id,
  rowId: Id,
  cellId: Id,
  newCell: Cell,
  oldCell: Cell,
  getCellChange: GetCellChange | undefined,
): void
TypeDescription
storeStore

A reference to the Store that changed.

tableIdId

The Id of the Table that changed.

rowIdId

The Id of the Row that changed.

cellIdId

The Id of the Cell that changed.

newCellCell

The new value of the Cell that changed.

oldCellCell

The old value of the Cell that changed.

getCellChangeGetCellChange | undefined

A function that returns information about any Cell's changes.

returnsvoid

This has no return value.

A CellListener is provided when using the addCellListener method. See that method for specific examples.

When called, a CellListener is given a reference to the Store, the Id of the Table that changed, the Id of the Row that changed, and the Id of Cell that changed. It is also given the new value of the Cell, the old value of the Cell, and a GetCellChange function that can be used to query Cell values before and after the current transaction.

Note that if the listener was manually forced to be called (with the callListener method rather than due to a real change in the Store), the GetCellChange function will not be present and the new and old values of the Cell will be the same.

GetCellChange

The GetCellChange type describes a function that returns information about any Cell's changes during a transaction.

(
  tableId: Id,
  rowId: Id,
  cellId: Id,
): CellChange
TypeDescription
tableIdId

The Id of the Table to inspect.

rowIdId

The Id of the Row to inspect.

cellIdId

The Id of the Cell to inspect.

returnsCellChange

A CellChange array containing information about the Cell's changes.

A GetCellChange function is provided to every listener when called due the Store changing. The listener can then fetch the previous value of a Cell before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.

InvalidCellListener

The InvalidCellListener type describes a function that is used to listen to attempts to set invalid data to a Cell.

(
  store: Store,
  tableId: Id,
  rowId: Id,
  cellId: Id,
  invalidCells: any[],
): void
TypeDescription
storeStore

A reference to the Store that was being changed.

tableIdId

The Id of the Table that was being changed.

rowIdId

The Id of the Row that was being changed.

cellIdId

The Id of the Cell that was being changed.

invalidCellsany[]

An array of the values of the Cell that were invalid.

returnsvoid

This has no return value.

A InvalidCellListener is provided when using the addInvalidCellListener method. See that method for specific examples.

When called, a InvalidCellListener is given a reference to the Store, the Id of the Table, the Id of the Row, and the Id of Cell that were being attempted to be changed. It is also given the invalid value of the Cell, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell within a single transaction, this is an array containing each attempt, chronologically.

Callback type aliases

This is the collection of callback type aliases within the store module. There are 5 callback type aliases in total.

TableCallback

The TableCallback type describes a function that takes a Table's Id and a callback to loop over each Row within it.

(
  tableId: Id,
  forEachRow: (rowCallback: RowCallback) => void,
): void
TypeDescription
tableIdId

The Id of the Table that the callback can operate on.

forEachRow(rowCallback: RowCallback) => void

A function that will let you iterate over the Row objects in this Table.

returnsvoid

This has no return value.

A TableCallback is provided when using the forEachTable method, so that you can do something based on every Table in the Store. See that method for specific examples.

RowCallback

The RowCallback type describes a function that takes a Row's Id and a callback to loop over each Cell within it.

(
  rowId: Id,
  forEachCell: (cellCallback: CellCallback) => void,
): void
TypeDescription
rowIdId

The Id of the Row that the callback can operate on.

forEachCell(cellCallback: CellCallback) => void
returnsvoid

This has no return value.

A RowCallback is provided when using the forEachRow method, so that you can do something based on every Row in a Table. See that method for specific examples.

CellCallback

The CellCallback type describes a function that takes a Cell's Id and its value.

(
  cellId: Id,
  cell: Cell,
): void
TypeDescription
cellIdId

The Id of the Cell that the callback can operate on.

cellCell

The value of the Cell.

returnsvoid

This has no return value.

A CellCallback is provided when using the forEachCell method, so that you can do something based on every Cell in a Row. See that method for specific examples.

GetCell

The GetCell type describes a function that takes a Id and returns the Cell value for a particular Row.

(cellId: Id): CellOrUndefined
TypeDescription
cellIdId

The Id of the Cell to fetch the value for.

returnsCellOrUndefined

A GetCell can be provided when setting definitions, as in the setMetricDefinition method of a Metrics object, or the setIndexDefinition method of an Indexes object. See those methods for specific examples.

MapCell

The MapCell type describes a function that takes an existing Cell value and returns another.

(cell: CellOrUndefined): Cell
TypeDescription
cellCellOrUndefined

The current value of the Cell to map to a new value.

returnsCell

A MapCell can be provided in the setCell method to map an existing value to a new one, such as when incrementing a number. See that method for specific examples.

Schema type aliases

This is the collection of schema type aliases within the store module. There are only two schema type aliases, CellSchema and Schema.

CellSchema

The CellSchema type describes what values are allowed for each Cell in a Table.

{
  type: "string";
  default?: string;
} | {
  type: "number";
  default?: number;
} | {
  type: "boolean";
  default?: boolean;
}

A CellSchema specifies the type of the Cell (string, boolean, or number), and what the default value can be when an explicit value is not specified.

If a default value is provided (and its type is correct), you can be certain that that Cell will always be present in a Row.

If the default value is not provided (or its type is incorrect), the Cell may be missing from the Row, but when present you can be guaranteed it is of the correct type.

Example

When applied to a Store, this CellSchema ensures a boolean Cell is always present, and defaults it to false.

const requiredBoolean: CellSchema = {type: 'boolean', default: false};
Schema

The Schema type describes the structure of a Store in terms of valid Table Ids and the types of Cell that can exist within them.

{[tableId: Id]: {[cellId: Id]: CellSchema}}

A Schema comprises a JavaScript object describing each Table, in turn a nested JavaScript object containing information about each Cell and its CellSchema. It is provided to the setSchema method.

Example

When applied to a Store, this Schema only allows one Table called pets, in which each Row may contain a string species Cell, and is guaranteed to contain a boolean sold Cell that defaults to false.

const schema: Schema = {
  pets: {
    species: {type: 'string'},
    sold: {type: 'boolean', default: false},
  },
};

Store type aliases

This is the collection of store type aliases within the store module. There are 5 store type aliases in total.

Tables

The Tables type is the data structure representing all of the data in a Store.

{[tableId: Id]: Table}

A Tables object is used when setting all of the tables together with the setTables method, and when getting them back out again with the getTables method. A Tables object is a regular JavaScript object containing individual Table objects, keyed by their Id.

Example
const tables: Tables = {
  pets: {
    fido: {species: 'dog', color: 'brown'},
    felix: {species: 'cat'},
  },
  species: {
    dog: {price: 5},
    cat: {price: 4},
  },
};
Table

The Table type is the data structure representing the data in a single table.

{[rowId: Id]: Row}

A Table is used when setting a table with the setTable method, and when getting it back out again with the getTable method. A Table object is a regular JavaScript object containing individual Row objects, keyed by their Id.

Example
const table: Table = {
  fido: {species: 'dog', color: 'brown'},
  felix: {species: 'cat'},
};
Row

The Row type is the data structure representing the data in a single row.

{[cellId: Id]: Cell}

A Row is used when setting a row with the setRow method, and when getting it back out again with the getRow method. A Row object is a regular JavaScript object containing individual Cell objects, keyed by their Id.

Example
const row: Row = {species: 'dog', color: 'brown'};
Cell

The Cell type is the data structure representing the data in a single cell.

string | number | boolean

A Cell is used when setting a cell with the setCell method, and when getting it back out again with the getCell method. A Cell is a JavaScript string, number, or boolean.

Example
const cell: Cell = 'dog';
CellOrUndefined

The CellOrUndefined type is a data structure representing the data in a single cell or the value undefined.

Cell | undefined

This is used when describing a Cell that is present or that is not present

  • such as when it has been deleted, or when describing a previous state where the Cell value has since been added.

Transaction type aliases

This is the collection of transaction type aliases within the store module. There are only two transaction type aliases, ChangedCells and InvalidCells.

ChangedCells

The ChangedCells type describes the Cell values that have been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.

{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: [CellOrUndefined, CellOrUndefined]}}}

A ChangedCells object is provided to the doRollback callback when using the transaction method. See that method for specific examples.

This type is a nested structure of Table, Row, and Cell objects, much like the Tables object, but one which provides both the old and new Cell values in a two-part array. These are describing the state of each changed Cell in Store at the start of the transaction, and by the end of the transaction.

Hence, an undefined value for the first item in the array means that the Cell was added during the transaction. An undefined value for the second item in the array means that the Cell was removed during the transaction. An array with two different Cell values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined values), even if, during the transaction, a Cell was changed to a different value and then changed back.

InvalidCells

The InvalidCells type describes the invalid Cell values that have been attempted during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.

{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: any[]}}}

An InvalidCells object is provided to the doRollback callback when using the transaction method. See that method for specific examples.

This type is a nested structure of Table, Row, and Cell objects, much like the Tables object, but one for which Cell values are listed in array (much like the InvalidCellListener type) so that multiple failed attempts to change a Cell during the transaction are described.

Development type aliases

This is the collection of development type aliases within the store module. There is only one type alias, StoreListenerStats.

StoreListenerStats

The StoreListenerStats type describes the number of listeners registered with the Store, and can be used for debugging purposes.

{
  tables?: number;
  tableIds?: number;
  table?: number;
  rowIds?: number;
  row?: number;
  cellIds?: number;
  cell?: number;
}
TypeDescription
tables?number

The number of TablesListeners registered with the Store.

tableIds?number

The number of TableIdsListeners registered with the Store.

table?number

The number of TableListeners registered with the Store.

rowIds?number

The number of RowIdsListeners registered with the Store.

row?number

The number of RowListeners registered with the Store.

cellIds?number

The number of CellIdsListeners registered with the Store.

cell?number

The number of CellListeners registered with the Store.

The StoreListenerStats object contains a breakdown of the different types of listener. Totals include both mutator and non-mutator listeners. A StoreListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.

metrics

The metrics module of the TinyBase project provides the ability to create and track metrics and aggregates of the data in Store objects.

The main entry point to this module is the createMetrics function, which returns a new Metrics object. From there, you can create new Metric definitions, access the values of those Metrics directly, and register listeners for when they change.

Interfaces

There is one interface, Metrics, within the metrics module.

Metrics

A Metrics object lets you define, query, and listen to, aggregations of Cell values within a Table in a Store.

This is useful for counting the number of Row objects in a Table, averaging Cell values, or efficiently performing any arbitrary aggregations.

Create a Metrics object easily with the createMetrics function. From there, you can add new Metric definitions (with the setMetricDefinition method), query their values (with the getMetric method), and add listeners for when they change (with the addMetricListener method).

This module provides a number of predefined and self-explanatory aggregations ('sum', 'avg', 'min', and 'max'), and defaults to counting Row objects when using the setMetricDefinition method. However, far more complex aggregations can be configured with custom functions.

Example

This example shows a very simple lifecycle of a Metrics object: from creation, to adding a definition, getting an Metric, and then registering and removing a listener for it.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition(
  'highestPrice', // metricId
  'species', //      tableId to aggregate
  'max', //          aggregation
  'price', //        cellId to aggregate
);

console.log(metrics.getMetric('highestPrice'));
// -> 5

const listenerId = metrics.addMetricListener('highestPrice', () => {
  console.log(metrics.getMetric('highestPrice'));
});
store.setCell('species', 'horse', 'price', 20);
// -> 20

metrics.delListener(listenerId);
metrics.destroy();
See also

Metrics And Indexes guides

Rolling Dice demos

Country demo

Todo App demos

Getter methods

This is the collection of getter methods within the Metrics interface. There are 5 getter methods in total.

getStore

The getStore method returns a reference to the underlying Store that is backing this Metrics object.

getStore(): Store
returnsStore

A reference to the Store.

Example

This example creates a Metrics object against a newly-created Store and then gets its reference in order to update its data.

const metrics = createMetrics(createStore());
metrics.setMetricDefinition('speciesCount', 'species');
metrics.getStore().setCell('species', 'dog', 'price', 5);
console.log(metrics.getMetric('speciesCount'));
// -> 1
getTableId

The getTableId method returns the Id of the underlying Table that is backing a Metric.

getTableId(metricId: string): undefined | string
TypeDescription
metricIdstring

The Id of a Metric.

returnsundefined | string

The Id of the Table backing the Metric, or `undefined`.

If the Metric Id is invalid, the method returns undefined.

Example

This example creates a Metrics object, a single Metric definition, and then queries it (and a non-existent definition) to get the underlying Table Id.

const metrics = createMetrics(createStore());
metrics.setMetricDefinition('speciesCount', 'species');

console.log(metrics.getTableId('speciesCount'));
// -> 'species'
console.log(metrics.getTableId('petsCount'));
// -> undefined
getMetric

The getMetric method gets the current value of a Metric.

getMetric(metricId: string): undefined | number
TypeDescription
metricIdstring

The Id of the Metric.

returnsundefined | number

The numeric value of the Metric, or `undefined`.

If the identified Metric does not exist (or if the definition references a Table or Cell value that does not exist) then undefined is returned.

Example

This example creates a Store, creates a Metrics object, and defines a simple Metric to average the price values in the Table. It then uses getMetric to access its value (and also the value of a Metric that has not been defined).

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

console.log(metrics.getMetric('highestPrice'));
// -> 5
console.log(metrics.getMetric('lowestPrice'));
// -> undefined
getMetricIds

The getMetricIds method returns an array of the Metric Ids registered with this Metrics object.

getMetricIds(): Ids
returnsIds

An array of Ids.

Example

This example creates a Metrics object with two definitions, and then gets the Ids of the definitions.

const metrics = createMetrics(createStore())
  .setMetricDefinition('speciesCount', 'species')
  .setMetricDefinition('petsCount', 'pets');

console.log(metrics.getMetricIds());
// -> ['speciesCount', 'petsCount']
hasMetric

The hasMetric method returns a boolean indicating whether a given Metric exists in the Metrics object, and has a value.

hasMetric(metricId: string): boolean
TypeDescription
metricIdstring

The Id of a possible Metric in the Metrics object.

returnsboolean

Whether a Metric with that Id exists.

Example

This example shows two simple Metric existence checks.

const store = createStore();
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

console.log(metrics.hasMetric('lowestPrice'));
// -> false
console.log(metrics.hasMetric('highestPrice'));
// -> false
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
console.log(metrics.hasMetric('highestPrice'));
// -> true
Listener methods

This is the collection of listener methods within the Metrics interface. There are only two listener methods, addMetricListener and delListener.

addMetricListener

The addMetricListener method registers a listener function with the Metrics object that will be called whenever the value of a specified Metric changes.

addMetricListener(
  metricId: IdOrNull,
  listener: MetricListener,
): string
TypeDescription
metricIdIdOrNull

The Id of the Metric to listen to, or null as a wildcard.

listenerMetricListener

The function that will be called whenever the Metric changes.

returnsstring

A unique Id for the listener that can later be used to remove it.

You can either listen to a single Metric (by specifying the Metric Id as the method's first parameter), or changes to any Metric (by providing a null wildcard).

The provided listener is a MetricListener function, and will be called with a reference to the Metrics object, the Id of the Metric that changed, the new Metric value, and the old Metric value.

Examples

This example creates a Store, a Metrics object, and then registers a listener that responds to any changes to a specific Metric.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

const listenerId = metrics.addMetricListener(
  'highestPrice',
  (metrics, metricId, newMetric, oldMetric) => {
    console.log('highestPrice metric changed');
    console.log([oldMetric, newMetric]);
  },
);

store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
// -> [5, 20]

metrics.delListener(listenerId);

This example creates a Store, a Metrics object, and then registers a listener that responds to any changes to any Metric.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store)
  .setMetricDefinition('highestPrice', 'species', 'max', 'price')
  .setMetricDefinition('speciesCount', 'species');

const listenerId = metrics.addMetricListener(
  null,
  (metrics, metricId, newMetric, oldMetric) => {
    console.log(`${metricId} metric changed`);
    console.log([oldMetric, newMetric]);
  },
);

store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'
// -> [5, 20]
// -> 'speciesCount metric changed'
// -> [3, 4]

metrics.delListener(listenerId);
delListener

The delListener method removes a listener that was previously added to the Metrics object.

delListener(listenerId: string): Metrics
TypeDescription
listenerIdstring

The Id of the listener to remove.

returnsMetrics

A reference to the Metrics object.

Use the Id returned by the addMetricListener method. Note that the Metrics object may re-use this Id for future listeners added to it.

Example

This example creates a Store, a Metrics object, registers a listener, and then removes it.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

const listenerId = metrics.addMetricListener(
  'highestPrice',
  (metrics, metricId, newMetric, oldMetric) => {
    console.log('highestPrice metric changed');
  },
);

store.setCell('species', 'horse', 'price', 20);
// -> 'highestPrice metric changed'

metrics.delListener(listenerId);

store.setCell('species', 'giraffe', 'price', 50);
// -> undefined
// The listener is not called.
Configuration methods

This is the collection of configuration methods within the Metrics interface. There are only two configuration methods, delMetricDefinition and setMetricDefinition.

delMetricDefinition

The delMetricDefinition method removes an existing Metric definition.

delMetricDefinition(metricId: string): Metrics
TypeDescription
metricIdstring

The Id of the Metric to remove.

returnsMetrics

A reference to the Metrics object.

Example

This example creates a Store, creates a Metrics object, defines a simple Metric, and then removes it.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(metrics.getMetricIds());
// -> ['speciesCount']

metrics.delMetricDefinition('speciesCount');
console.log(metrics.getMetricIds());
// -> []
setMetricDefinition

The setMetricDefinition method lets you set the definition of a Metric.

setMetricDefinition(
  metricId: string,
  tableId: string,
  aggregate?: Aggregate | "sum" | "avg" | "min" | "max",
  getNumber?: string | (getCell: GetCell, rowId: string) => number,
  aggregateAdd?: AggregateAdd,
  aggregateRemove?: AggregateRemove,
  aggregateReplace?: AggregateReplace,
): Metrics
TypeDescription
metricIdstring

The Id of the Metric to define.

tableIdstring

The Id of the Table the Metric will be calculated from.

aggregate?Aggregate | "sum" | "avg" | "min" | "max"

Either a string representing one of a set of common aggregation techniques ('sum', 'avg', 'min', or 'max'), or a function that aggregates numeric values from each Row to create the Metric's overall value. Defaults to 'sum'.

getNumber?string | (getCell: GetCell, rowId: string) => number

Either the Id of a Cell containing, or a function that produces, the numeric value that will be aggregated in the way specified by the aggregate parameter. Defaults to a function that returns 1 (meaning that if the aggregate and getNumber parameters are both omitted, the Metric will simply be a count of the Row objects in the Table).

aggregateAdd?AggregateAdd

A function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is added to the input values - for example, when a Row is added to the Table.

aggregateRemove?AggregateRemove

A function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is removed from the input values - for example ,when a Row is removed from the Table.

aggregateReplace?AggregateReplace

A function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value in the input values is replaced with another - for example, when a Row is updated.

returnsMetrics

A reference to the Metrics object.

Every Metric definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.

A Metric is an aggregation of numeric values produced from each Row within a single Table. Therefore the definition must specify the Table (by its Id) to be aggregated.

Without the third aggregate parameter, the Metric will simply be a count of the number of Row objects in the Table. But often you will specify a more interesting aggregate - such as the four predefined aggregates, 'sum', 'avg', 'min', and 'max' - or a custom function that produces your own aggregation of an array of numbers.

The fourth getNumber parameter specifies which Cell in each Row contains the numerical values to be used in the aggregation. Alternatively, a custom function can be provided that produces your own numeric value from the local Row as a whole.

The final three parameters, aggregateAdd, aggregateRemove, aggregateReplace need only be provided when you are using your own custom aggregate function. These give you the opportunity to reduce your custom function's algorithmic complexity by providing shortcuts that can nudge an aggregation result when a single value is added, removed, or replaced in the input values.

Examples

This example creates a Store, creates a Metrics object, and defines a simple Metric to count the Row objects in the Table.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');

console.log(metrics.getMetric('speciesCount'));
// -> 3

This example creates a Store, creates a Metrics object, and defines a standard Metric to get the highest value of each price Cell in the Row objects in the Table.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

console.log(metrics.getMetric('highestPrice'));
// -> 5

This example creates a Store, creates a Metrics object, and defines a custom Metric to get the lowest value of each price Cell, greater than 2.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition(
  'lowestPriceOver2',
  'species',
  (numbers) => Math.min(...numbers.filter((number) => number > 2)),
  'price',
);

console.log(metrics.getMetric('lowestPriceOver2'));
// -> 4

This example also creates a Store, creates a Metrics object, and defines a custom Metric to get the lowest value of each price Cell, greater than 2. However, it also reduces algorithmic complexity with two shortcut functions.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition(
  'lowestPriceOver2',
  'species',
  (numbers) => Math.min(...numbers.filter((number) => number > 2)),
  'price',
  (metric, add) => (add > 2 ? Math.min(metric, add) : metric),
  (metric, remove) => (remove == metric ? undefined : metric),
  (metric, add, remove) =>
    remove == metric
      ? undefined
      : add > 2
      ? Math.min(metric, add)
      : metric,
);

console.log(metrics.getMetric('lowestPriceOver2'));
// -> 4
store.setRow('species', 'fish', {price: 3});
console.log(metrics.getMetric('lowestPriceOver2'));
// -> 3

This example creates a Store, creates a Metrics object, and defines a custom Metric to get the average value of a discounted price.

const store = createStore().setTable('species', {
  dog: {price: 5, discount: 0.3},
  cat: {price: 4, discount: 0.2},
  worm: {price: 1, discount: 0.2},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition(
  'averageDiscountedPrice',
  'species',
  'avg',
  (getCell) => getCell('price') * (1 - getCell('discount')),
);

console.log(metrics.getMetric('averageDiscountedPrice'));
// -> 2.5
Iterator methods

This is the collection of iterator methods within the Metrics interface. There is only one method, forEachMetric.

forEachMetric

The forEachMetric method takes a function that it will then call for each Metric in the Metrics object.

forEachMetric(metricCallback: MetricCallback): void
TypeDescription
metricCallbackMetricCallback

The function that should be called for every Metric.

returnsvoid

This has no return value.

This method is useful for iterating over all the Metrics in a functional style. The metricCallback parameter is a MetricCallback function that will be called with the Id of each Metric and its value.

Example

This example iterates over each Metric in a Metrics object.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});
const metrics = createMetrics(store)
  .setMetricDefinition('highestPrice', 'species', 'max', 'price')
  .setMetricDefinition('lowestPrice', 'species', 'min', 'price');

metrics.forEachMetric((metricId, metric) => {
  console.log([metricId, metric]);
});
// -> ['highestPrice', 5]
// -> ['lowestPrice', 1]
Lifecycle methods

This is the collection of lifecycle methods within the Metrics interface. There is only one method, destroy.

destroy

The destroy method should be called when this Metrics object is no longer used.

destroy(): void

This guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.

Example

This example creates a Store, adds a Metrics object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});

const metrics = createMetrics(store);
metrics.setMetricDefinition('speciesCount', 'species');
console.log(store.getListenerStats().row);
// -> 1

metrics.destroy();

console.log(store.getListenerStats().row);
// -> 0
Development methods

This is the collection of development methods within the Metrics interface. There is only one method, getListenerStats.

getListenerStats

The getListenerStats method provides a set of statistics about the listeners registered with the Metrics object, and is used for debugging purposes.

getListenerStats(): MetricsListenerStats
returnsMetricsListenerStats

A MetricsListenerStats object containing Metrics listener statistics.

The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.

Example

This example gets the listener statistics of a Metrics object.

const store = createStore();
const metrics = createMetrics(store);
metrics.addMetricListener(null, () => console.log('Metric changed'));

console.log(metrics.getListenerStats());
// -> {metric: 1}

Functions

There is one function, createMetrics, within the metrics module.

createMetrics

The createMetrics function creates a Metrics object, and is the main entry point into the metrics module.

createMetrics(store: Store): Metrics
TypeDescription
storeStore

The Store for which to register Metric definitions.

returnsMetrics

A reference to the new Metrics object.

It is trivially simple.

A given Store can only have one Metrics object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Metrics object created by the first.

Examples

This example creates a Metrics object.

const store = createStore();
const metrics = createMetrics(store);
console.log(metrics.getMetricIds());
// -> []

This example creates a Metrics object, and calls the method a second time for the same Store to return the same object.

const store = createStore();
const metrics1 = createMetrics(store);
const metrics2 = createMetrics(store);
console.log(metrics1 === metrics2);
// -> true

Type aliases

These are the type aliases within the metrics module.

Listener type aliases

This is the collection of listener type aliases within the metrics module. There is only one type alias, MetricListener.

MetricListener

The MetricListener type describes a function that is used to listen to changes to a Metric.

(
  metrics: Metrics,
  metricId: Id,
  newMetric: Metric | undefined,
  oldMetric: Metric | undefined,
): void
TypeDescription
metricsMetrics

A reference to the Metrics object that changed.

metricIdId

The Id of the Metric that changed.

newMetricMetric | undefined

The new value of the Metric that changed.

oldMetricMetric | undefined

The old value of the Metric that changed.

returnsvoid

This has no return value.

A MetricListener is provided when using the addMetricListener method. See that method for specific examples.

When called, a MetricListener is given a reference to the Metrics object, the Id of the Metric that changed, and the new and old values of the Metric.

If this is the first time that a Metric has had a value (such as when a table has gained its first row), the old value will be undefined. If a Metric now no longer has a value, the new value will be undefined.

Aggregators type aliases

This is the collection of aggregators type aliases within the metrics module. There are 4 aggregators type aliases in total.

Aggregate

The Aggregate type describes a custom function that takes an array of numbers and returns an aggregate that is used as a Metric.

(
  numbers: number[],
  length: number,
): Metric
TypeDescription
numbersnumber[]

The array of numbers in the Metric's aggregation.

lengthnumber

The length of the array of numbers in the Metric's aggregation.

returnsMetric

The value of the Metric.

There are a number of common predefined aggregators, such as for counting, summing, and averaging values. This type is instead used for when you wish to use a more complex aggregation of your own devising. See the setMetricDefinition method for more examples.

AggregateAdd

The AggregateAdd type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is added to the input values.

(
  metric: Metric,
  add: number,
  length: number,
): Metric | undefined
TypeDescription
metricMetric

The current value of the Metric.

addnumber

The number being added to the Metric's aggregation.

lengthnumber

The length of the array of numbers in the Metric's aggregation.

returnsMetric | undefined

The new value of the Metric.

Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when adding a new number to a series, the new sum of the series is the new value added to the previous sum.

If it is not possible to shortcut the aggregation based on just one value being added, return undefined and the Metric will be completely recalculated.

Where possible, if you are providing a custom Aggregate, seek an implementation of an AggregateAdd function that can reduce the complexity cost of growing the input data set. See the setMetricDefinition method for more examples.

AggregateRemove

The AggregateRemove type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is removed from the input values.

(
  metric: Metric,
  remove: number,
  length: number,
): Metric | undefined
TypeDescription
metricMetric

The current value of the Metric.

removenumber

The number being removed from the Metric's aggregation.

lengthnumber

The length of the array of numbers in the Metric's aggregation.

returnsMetric | undefined

The new value of the Metric.

Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when removing a number from a series, the new sum of the series is the new value subtracted from the previous sum.

If it is not possible to shortcut the aggregation based on just one value being removed, return undefined and the Metric will be completely recalculated. One example might be if you were taking the minimum of the values, and the previous minimum is being removed. The whole of the rest of the list will need to be re-scanned to find a new minimum.

Where possible, if you are providing a custom Aggregate, seek an implementation of an AggregateRemove function that can reduce the complexity cost of shrinking the input data set. See the setMetricDefinition method for more examples.

AggregateReplace

The AggregateReplace type describes a function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value in the input values is replaced with another.

(
  metric: Metric,
  add: number,
  remove: number,
  length: number,
): Metric | undefined
TypeDescription
metricMetric

The current value of the Metric.

addnumber

The number being added to the Metric's aggregation.

removenumber

The number being removed from the Metric's aggregation.

lengthnumber

The length of the array of numbers in the Metric's aggregation.

returnsMetric | undefined

The new value of the Metric.

Some aggregation functions do not need to recalculate the aggregation of the whole set when one value changes. For example, when replacing a number in a series, the new sum of the series is the previous sum, plus the new value, minus the old value.

If it is not possible to shortcut the aggregation based on just one value changing, return undefined and the Metric will be completely recalculated.

Where possible, if you are providing a custom Aggregate, seek an implementation of an AggregateReplace function that can reduce the complexity cost of changing the input data set in place. See the setMetricDefinition method for more examples.

Callback type aliases

This is the collection of callback type aliases within the metrics module. There is only one type alias, MetricCallback.

MetricCallback

The MetricCallback type describes a function that takes a Metric's Id and a callback to loop over each Row within it.

(
  metricId: Id,
  metric?: Metric,
): void
TypeDescription
metricIdId

The Id of the Metric that the callback can operate on.

metric?Metric

The value of the Metric.

returnsvoid

This has no return value.

A MetricCallback is provided when using the forEachMetric method, so that you can do something based on every Metric in the Metrics object. See that method for specific examples.

Metric type aliases

This is the collection of metric type aliases within the metrics module. There is only one type alias, Metric.

Metric

The Metric type is simply an alias, but represents a number formed by aggregating multiple other numbers together.

number

Development type aliases

This is the collection of development type aliases within the metrics module. There is only one type alias, MetricsListenerStats.

MetricsListenerStats

The MetricsListenerStats type describes the number of listeners registered with the Metrics object, and can be used for debugging purposes.

{metric?: number}
TypeDescription
metric?number

The number of MetricListeners registered with the Metrics object.

A MetricsListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.

indexes

The indexes module of the TinyBase project provides the ability to create and track indexes of the data in Store objects.

The main entry point to this module is the createIndexes function, which returns a new Indexes object. From there, you can create new Index definitions, access the contents of those Indexes directly, and register listeners for when they change.

Interfaces

There is one interface, Indexes, within the indexes module.

Indexes

An Indexes object lets you look up all the Row objects in a Table that have a certain Cell value.

This is useful for creating filtered views of a Table, or simple search functionality.

Create an Indexes object easily with the createIndexes function. From there, you can add new Index definitions (with the setIndexDefinition method), query their contents (with the getSliceIds method and getSliceRowIds method), and add listeners for when they change (with the addSliceIdsListener method and addSliceRowIdsListener method).

This module defaults to indexing Row objects by one of their Cell values. However, far more complex indexes can be configured with a custom function.

Example

This example shows a very simple lifecycle of an Indexes object: from creation, to adding a definition, getting its contents, and then registering and removing a listener for it.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition(
  'bySpecies', // indexId
  'pets', //      tableId to index
  'species', //   cellId to index
);

console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']

const listenerId = indexes.addSliceIdsListener('bySpecies', () => {
  console.log(indexes.getSliceIds('bySpecies'));
});
store.setRow('pets', 'lowly', {species: 'worm'});
// -> ['dog', 'cat', 'worm']

indexes.delListener(listenerId);
indexes.destroy();
See also

Metrics And Indexes guides

Rolling Dice demos

Country demo

Todo App demos

Getter methods

This is the collection of getter methods within the Indexes interface. There are 7 getter methods in total.

getStore

The getStore method returns a reference to the underlying Store that is backing this Indexes object.

getStore(): Store
returnsStore

A reference to the Store.

Example

This example creates an Indexes object against a newly-created Store and then gets its reference in order to update its data.

const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
indexes.getStore().setCell('pets', 'fido', 'species', 'dog');
console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog']
getTableId

The getTableId method returns the Id of the underlying Table that is backing an Index.

getTableId(indexId: string): string
TypeDescription
indexIdstring

The Id of an Index.

returnsstring

The Id of the Table backing the Index, or `undefined`.

If the Index Id is invalid, the method returns undefined.

Example

This example creates an Indexes object, a single Index definition, and then queries it (and a non-existent definition) to get the underlying Table Id.

const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

console.log(indexes.getTableId('bySpecies'));
// -> 'pets'
console.log(indexes.getTableId('byColor'));
// -> undefined
getSliceRowIds

The getSliceRowIds method gets the list of Row Ids in a given Slice, within a given Index.

getSliceRowIds(
  indexId: string,
  sliceId: string,
): Ids
TypeDescription
indexIdstring

The Id of the Index.

sliceIdstring

The Id of the Slice in the Index.

returnsIds

The Row Ids in the Slice, or an empty array.

If the identified Index or Slice do not exist (or if the definition references a Table that does not exist) then an empty array is returned.

Example

This example creates a Store, creates an Indexes object, and defines a simple Index. It then uses getSliceRowIds to see the Row Ids in the Slice (and also the Row Ids in Slices that do not exist).

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(indexes.getSliceRowIds('bySpecies', 'worm'));
// -> []
console.log(indexes.getSliceRowIds('byColor', 'brown'));
// -> []
getIndexIds

The getIndexIds method returns an array of the Index Ids registered with this Indexes object.

getIndexIds(): Ids
returnsIds

An array of Ids.

Example

This example creates an Indexes object with two definitions, and then gets the Ids of the definitions.

const indexes = createIndexes(createStore())
  .setIndexDefinition('bySpecies', 'pets', 'species')
  .setIndexDefinition('byColor', 'pets', 'color');

console.log(indexes.getIndexIds());
// -> ['bySpecies', 'byColor']
getSliceIds

The getSliceIds method gets the list of Slice Ids in an Index.

getSliceIds(indexId: string): Ids
TypeDescription
indexIdstring

The Id of the Index.

returnsIds

The Slice Ids in the Index, or an empty array.

If the identified Index does not exist (or if the definition references a Table that does not exist) then an empty array is returned.

Example

This example creates a Store, creates an Indexes object, and defines a simple Index. It then uses getSliceIds to see the available Slice Ids in the Index (and also the Slice Ids in an Index that has not been defined).

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceIds('byColor'));
// -> []
hasIndex

The hasIndex method returns a boolean indicating whether a given Index exists in the Indexes object.

hasIndex(indexId: string): boolean
TypeDescription
indexIdstring

The Id of a possible Index in the Indexes object.

returnsboolean

Whether an Index with that Id exists.

Example

This example shows two simple Index existence checks.

const indexes = createIndexes(createStore());
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.hasIndex('bySpecies'));
// -> true
console.log(indexes.hasIndex('byColor'));
// -> false
hasSlice

The hasSlice method returns a boolean indicating whether a given Slice exists in the Indexes object.

hasSlice(
  indexId: string,
  sliceId: string,
): boolean
TypeDescription
indexIdstring

The Id of a possible Index in the Indexes object.

sliceIdstring

The Id of a possible Slice in the Index.

returnsboolean

Whether a Slice with that Id exists.

Example

This example shows two simple Index existence checks.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.hasSlice('bySpecies', 'dog'));
// -> true
console.log(indexes.hasSlice('bySpecies', 'worm'));
// -> false
Listener methods

This is the collection of listener methods within the Indexes interface. There are only three listener methods, addSliceRowIdsListener, addSliceIdsListener, and delListener.

addSliceRowIdsListener

The addSliceRowIdsListener method registers a listener function with the Indexes object that will be called whenever the Row Ids in a Slice change.

addSliceRowIdsListener(
  indexId: IdOrNull,
  sliceId: IdOrNull,
  listener: SliceRowIdsListener,
): string
TypeDescription
indexIdIdOrNull

The Id of the Index to listen to, or null as a wildcard.

sliceIdIdOrNull

The Id of the Slice to listen to, or null as a wildcard.

listenerSliceRowIdsListener

The function that will be called whenever the Row Ids in the Slice change.

returnsstring

A unique Id for the listener that can later be used to remove it.

You can either listen to a single Slice (by specifying the Index Id and Slice Id as the method's first two parameters), or changes to any Slice (by providing null wildcards).

Both, either, or neither of the indexId and sliceId parameters can be wildcarded with null. You can listen to a specific Slice in a specific Index, any Slice in a specific Index, a specific Slice in any Index, or any Slice in any Index.

The provided listener is a SliceRowIdsListener function, and will be called with a reference to the Indexes object, the Id of the Index, and the Id of the Slice that changed.

Examples

This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to a specific Slice.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const listenerId = indexes.addSliceRowIdsListener(
  'bySpecies',
  'dog',
  (indexes, indexId, sliceId) => {
    console.log('Row Ids for dog slice in bySpecies index changed');
    console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
  },
);

store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Row Ids for dog slice in bySpecies index changed'
// -> ['fido', 'cujo', 'toto']

indexes.delListener(listenerId);

This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to any Slice.

const store = createStore().setTable('pets', {
  fido: {species: 'dog', color: 'brown'},
  felix: {species: 'cat', color: 'black'},
  cujo: {species: 'dog', color: 'black'},
});

const indexes = createIndexes(store)
  .setIndexDefinition('bySpecies', 'pets', 'species')
  .setIndexDefinition('byColor', 'pets', 'color');

const listenerId = indexes.addSliceRowIdsListener(
  null,
  null,
  (indexes, indexId, sliceId) => {
    console.log(
      `Row Ids for ${sliceId} slice in ${indexId} index changed`,
    );
    console.log(indexes.getSliceRowIds(indexId, sliceId));
  },
);

store.setRow('pets', 'toto', {species: 'dog', color: 'brown'});
// -> 'Row Ids for dog slice in bySpecies index changed'
// -> ['fido', 'cujo', 'toto']
// -> 'Row Ids for brown slice in byColor index changed'
// -> ['fido', 'toto']

indexes.delListener(listenerId);
addSliceIdsListener

The addSliceIdsListener method registers a listener function with the Indexes object that will be called whenever the Slice Ids in an Index change.

addSliceIdsListener(
  indexId: IdOrNull,
  listener: SliceIdsListener,
): string
TypeDescription
indexIdIdOrNull

The Id of the Index to listen to, or null as a wildcard.

listenerSliceIdsListener

The function that will be called whenever the Slice Ids in the Index change.

returnsstring

A unique Id for the listener that can later be used to remove it.

You can either listen to a single Index (by specifying the Index Id as the method's first parameter), or changes to any Index (by providing a null wildcard).

The provided listener is a SliceIdsListener function, and will be called with a reference to the Indexes object, and the Id of the Index that changed.

Examples

This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to a specific Index.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const listenerId = indexes.addSliceIdsListener(
  'bySpecies',
  (indexes, indexId) => {
    console.log('Slice Ids for bySpecies index changed');
    console.log(indexes.getSliceIds('bySpecies'));
  },
);

store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids for bySpecies index changed'
// -> ['dog', 'cat', 'worm']

indexes.delListener(listenerId);

This example creates a Store, an Indexes object, and then registers a listener that responds to any changes to any Index.

const store = createStore().setTable('pets', {
  fido: {species: 'dog', color: 'brown'},
  felix: {species: 'cat', color: 'black'},
  cujo: {species: 'dog', color: 'brown'},
});

const indexes = createIndexes(store)
  .setIndexDefinition('bySpecies', 'pets', 'species')
  .setIndexDefinition('byColor', 'pets', 'color');

const listenerId = indexes.addSliceIdsListener(
  null,
  (indexes, indexId) => {
    console.log(`Slice Ids for ${indexId} index changed`);
    console.log(indexes.getSliceIds(indexId));
  },
);

store.setRow('pets', 'lowly', {species: 'worm', color: 'pink'});
// -> 'Slice Ids for bySpecies index changed'
// -> ['dog', 'cat', 'worm']
// -> 'Slice Ids for byColor index changed'
// -> ['brown', 'black', 'pink']

indexes.delListener(listenerId);
delListener

The delListener method removes a listener that was previously added to the Indexes object.

delListener(listenerId: string): Indexes
TypeDescription
listenerIdstring

The Id of the listener to remove.

returnsIndexes

A reference to the Indexes object.

Use the Id returned by whichever method was used to add the listener. Note that the Indexes object may re-use this Id for future listeners added to it.

Example

This example creates a Store, an Indexes object, registers a listener, and then removes it.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const listenerId = indexes.addSliceIdsListener(
  'bySpecies',
  (indexes, indexId) => {
    console.log('Slice Ids for bySpecies index changed');
  },
);

store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids for bySpecies index changed'

indexes.delListener(listenerId);

store.setRow('pets', 'toto', {species: 'dog'});
// -> undefined
// The listener is not called.
Configuration methods

This is the collection of configuration methods within the Indexes interface. There are only two configuration methods, delIndexDefinition and setIndexDefinition.

delIndexDefinition

The delIndexDefinition method removes an existing Index definition.

delIndexDefinition(indexId: string): Indexes
TypeDescription
indexIdstring

The Id of the Index to remove.

returnsIndexes

A reference to the Indexes object.

Example

This example creates a Store, creates an Indexes object, defines a simple Index, and then removes it.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(indexes.getIndexIds());
// -> ['bySpecies']

indexes.delIndexDefinition('bySpecies');
console.log(indexes.getIndexIds());
// -> []
setIndexDefinition

The setIndexDefinition method lets you set the definition of an Index.

setIndexDefinition(
  indexId: string,
  tableId: string,
  getSliceId?: string | (getCell: GetCell, rowId: string) => string,
  getSortKey?: string | (getCell: GetCell, rowId: string) => SortKey,
  sliceIdSorter?: (sliceId1: string, sliceId2: string) => number,
  rowIdSorter?: (sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number,
): Indexes
TypeDescription
indexIdstring

The Id of the Index to define.

tableIdstring

The Id of the Table the Index will be generated from.

getSliceId?string | (getCell: GetCell, rowId: string) => string

Either the Id of the Cell containing, or a function that produces, the Id that is used to indicate which Slice in the Index the Row Id should be in. Defaults to a function that returns '' (meaning that if this getSliceId parameter is omitted, the Index will simply contain a single Slice containing all the Row Ids in the Table).

getSortKey?string | (getCell: GetCell, rowId: string) => SortKey

Either the Id of the Cell containing, or a function that produces, the value that is used to sort the Row Ids in each Slice.

sliceIdSorter?(sliceId1: string, sliceId2: string) => number

A function that takes two Slice Id values and returns a positive or negative number to indicate how they should be sorted.

rowIdSorter?(sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number

A function that takes two Row Id values (and a slice Id) and returns a positive or negative number to indicate how they should be sorted.

returnsIndexes

A reference to the Indexes object.

Every Index definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.

An Index is a keyed map of Slice objects, each of which is a list of Row Ids from a given Table. Therefore the definition must specify the Table (by its Id) to be indexed.

The Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by this method. Those values are used as the key for each Slice in the overall Index object.

Without the third getSliceId parameter, the Index will simply have a single Slice, keyed by an empty string. But more often you will specify a Cell value containing the Slice Id that the Row should belong to. Alternatively, a custom function can be provided that produces your own Slice Id from the local Row as a whole.

The fourth getSortKey parameter specifies a Cell Id to get a value (or a function that processes a whole Row to get a value) that is used to sort the Row Ids within each Slice in the Index.

The fifth parameter, sliceIdSorter, lets you specify a way to sort the Slice Ids when you access the Index, which may be useful if you are trying to create an alphabetic Index of Row entries. If not specified, the order of the Slice Ids will match the order of Row insertion.

The final parameter, rowIdSorter, lets you specify a way to sort the Row Ids within each Slice, based on the getSortKey parameter. This may be useful if you are trying to keep Rows in a determined order relative to each other in the Index. If omitted, the Row Ids are sorted alphabetically, based on the getSortKey parameter.

The two 'sorter' parameters, sliceIdSorter and rowIdSorter, are functions that take two values and return a positive or negative number for when they are in the wrong or right order, respectively. This is exactly the same as the 'compareFunction' that is used in the standard JavaScript array sort method, with the addition that rowIdSorter also takes the Slice Id parameter, in case you want to sort Row Ids differently in each Slice. You can use the convenient defaultSorter function to default this to be alphanumeric.

Examples

This example creates a Store, creates an Indexes object, and defines a simple Index based on the values in the species Cell.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

console.log(indexes.getSliceIds('bySpecies'));
// -> ['dog', 'cat']
console.log(indexes.getSliceRowIds('bySpecies', 'dog'));
// -> ['fido', 'cujo']

This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('byFirst', 'pets', (_, rowId) => rowId[0]);

console.log(indexes.getSliceIds('byFirst'));
// -> ['f', 'c']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['fido', 'felix']

This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names. The Slice Ids (and Row Ids within them) are alphabetically sorted.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition(
  'byFirst', //              indexId
  'pets', //                 tableId
  (_, rowId) => rowId[0], // each Row's sliceId
  (_, rowId) => rowId, //    each Row's sort key
  defaultSorter, //          sort Slice Ids
  defaultSorter, //          sort Row Ids
);

console.log(indexes.getSliceIds('byFirst'));
// -> ['c', 'f']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['felix', 'fido']
Iterator methods

This is the collection of iterator methods within the Indexes interface. There are only two iterator methods, forEachIndex and forEachSlice.

forEachIndex

The forEachIndex method takes a function that it will then call for each Index in a specified Indexes object.

forEachIndex(indexCallback: IndexCallback): void
TypeDescription
indexCallbackIndexCallback

The function that should be called for every Index.

returnsvoid

This has no return value.

This method is useful for iterating over the structure of the Indexes object in a functional style. The indexCallback parameter is a IndexCallback function that will be called with the Id of each Index, and with a function that can then be used to iterate over each Slice of the Index, should you wish.

Example

This example iterates over each Index in an Indexes object, and lists each Slice Id within them.

const store = createStore().setTable('pets', {
  fido: {species: 'dog', color: 'brown'},
  felix: {species: 'cat', color: 'black'},
  cujo: {species: 'dog', color: 'black'},
});
const indexes = createIndexes(store)
  .setIndexDefinition('bySpecies', 'pets', 'species')
  .setIndexDefinition('byColor', 'pets', 'color');

indexes.forEachIndex((indexId, forEachSlice) => {
  console.log(indexId);
  forEachSlice((sliceId) => console.log(`- ${sliceId}`));
});
// -> 'bySpecies'
// -> '- dog'
// -> '- cat'
// -> 'byColor'
// -> '- brown'
// -> '- black'
forEachSlice

The forEachSlice method takes a function that it will then call for each Slice in a specified Index.

forEachSlice(
  indexId: string,
  sliceCallback: SliceCallback,
): void
TypeDescription
indexIdstring

The Id of the Index to iterate over.

sliceCallbackSliceCallback

The function that should be called for every Slice.

returnsvoid

This has no return value.

This method is useful for iterating over the Slice structure of the Index in a functional style. The rowCallback parameter is a RowCallback function that will be called with the Id and value of each Row in the Slice.

Example

This example iterates over each Row in a Slice, and lists its Id.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

indexes.forEachSlice('bySpecies', (sliceId, forEachRow) => {
  console.log(sliceId);
  forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'dog'
// -> '- fido'
// -> '- cujo'
// -> 'cat'
// -> '- felix'
Lifecycle methods

This is the collection of lifecycle methods within the Indexes interface. There is only one method, destroy.

destroy

The destroy method should be called when this Indexes object is no longer used.

destroy(): void

This guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.

Example

This example creates a Store, adds an Indexes object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
console.log(store.getListenerStats().row);
// -> 1

indexes.destroy();

console.log(store.getListenerStats().row);
// -> 0
Development methods

This is the collection of development methods within the Indexes interface. There is only one method, getListenerStats.

getListenerStats

The getListenerStats method provides a set of statistics about the listeners registered with the Indexes object, and is used for debugging purposes.

getListenerStats(): IndexesListenerStats
returnsIndexesListenerStats

A IndexesListenerStats object containing Indexes listener statistics.

The IndexesListenerStats object contains a breakdown of the different types of listener.

The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.

Example

This example gets the listener statistics of an Indexes object.

const store = createStore();
const indexes = createIndexes(store);
indexes.addSliceIdsListener(null, () => {
  console.log('Slice Ids changed');
});
indexes.addSliceRowIdsListener(null, null, () => {
  console.log('Slice Row Ids changed');
});

console.log(indexes.getListenerStats());
// -> {sliceIds: 1, sliceRowIds: 1}

Functions

There is one function, createIndexes, within the indexes module.

createIndexes

The createIndexes function creates an Indexes object, and is the main entry point into the indexes module.

createIndexes(store: Store): Indexes
TypeDescription
storeStore

The Store for which to register Index definitions.

returnsIndexes

A reference to the new Indexes object.

It is trivially simple.

A given Store can only have one Indexes object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Indexes object created by the first.

Examples

This example creates an Indexes object.

const store = createStore();
const indexes = createIndexes(store);
console.log(indexes.getIndexIds());
// -> []

This example creates an Indexes object, and calls the method a second time for the same Store to return the same object.

const store = createStore();
const indexes1 = createIndexes(store);
const indexes2 = createIndexes(store);
console.log(indexes1 === indexes2);
// -> true

Type aliases

These are the type aliases within the indexes module.

Listener type aliases

This is the collection of listener type aliases within the indexes module. There are only two listener type aliases, SliceRowIdsListener and SliceIdsListener.

SliceRowIdsListener

The SliceRowIdsListener type describes a function that is used to listen to changes to the Row Ids in a Slice.

(
  indexes: Indexes,
  indexId: Id,
  sliceId: Id,
): void
TypeDescription
indexesIndexes

A reference to the Indexes object that changed.

indexIdId

The Id of the Index that changed.

sliceIdId

The Id of the Slice that changed.

returnsvoid

This has no return value.

A SliceRowIdsListener is provided when using the addSliceRowIdsListener method. See that method for specific examples.

When called, a SliceRowIdsListener is given a reference to the Indexes object, the Id of the Index that changed, and the Id of the Slice whose Row Ids changed.

SliceIdsListener

The SliceIdsListener type describes a function that is used to listen to changes to the Slice Ids in an Index.

(
  indexes: Indexes,
  indexId: Id,
): void
TypeDescription
indexesIndexes

A reference to the Indexes object that changed.

indexIdId

The Id of the Index that changed.

returnsvoid

This has no return value.

A SliceIdsListener is provided when using the addSliceIdsListener method. See that method for specific examples.

When called, a SliceIdsListener is given a reference to the Indexes object, and the Id of the Index that changed.

Callback type aliases

This is the collection of callback type aliases within the indexes module. There are only two callback type aliases, IndexCallback and SliceCallback.

IndexCallback

The IndexCallback type describes a function that takes an Index's Id and a callback to loop over each Slice within it.

(
  indexId: Id,
  forEachSlice: (sliceCallback: SliceCallback) => void,
): void
TypeDescription
indexIdId

The Id of the Index that the callback can operate on.

forEachSlice(sliceCallback: SliceCallback) => void
returnsvoid

This has no return value.

A IndexCallback is provided when using the forEachIndex method, so that you can do something based on every Index in the Indexes object. See that method for specific examples.

SliceCallback

The SliceCallback type describes a function that takes a Slice's Id and a callback to loop over each Row within it.

(
  sliceId: Id,
  forEachRow: (rowCallback: RowCallback) => void,
): void
TypeDescription
sliceIdId

The Id of the Slice that the callback can operate on.

forEachRow(rowCallback: RowCallback) => void

A function that will let you iterate over the Row objects in this Slice.

returnsvoid

This has no return value.

A SliceCallback is provided when using the forEachSlice method, so that you can do something based on every Slice in an Index. See that method for specific examples.

Concept type aliases

This is the collection of concept type aliases within the indexes module. There are only two concept type aliases, Index and Slice.

Index

The Index type represents the concept of a map of Slice objects, keyed by Id.

{[sliceId: Id]: Slice}

The Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by the setIndexDefinition method. Those values are used as the key for each Slice in the overall Index object.

Note that the Index type is not actually used in the API, and you instead enumerate and access its structure with the getSliceIds method and getSliceRowIds method.

Slice

The Slice type represents the concept of a set of Row objects that comprise part of an Index.

Ids

The Ids in a Slice represent Row objects from a Table that all have a derived string value in common, as described by the setIndexDefinition method.

Note that the Slice type is not actually used in the API, and you instead get Row Ids directly with the getSliceRowIds method.

Development type aliases

This is the collection of development type aliases within the indexes module. There is only one type alias, IndexesListenerStats.

IndexesListenerStats

The IndexesListenerStats type describes the number of listeners registered with the Indexes object, and can be used for debugging purposes.

{
  sliceIds?: number;
  sliceRowIds?: number;
}
TypeDescription
sliceIds?number

The number of SlideIdsListeners registered with the Indexes object.

sliceRowIds?number

The number of SliceRowIdsListeners registered with the Indexes object.

A IndexesListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.

relationships

The relationships module of the TinyBase project provides the ability to create and track relationships between the data in Store objects.

The main entry point to this module is the createRelationships function, which returns a new Relationships object. From there, you can create new Relationship definitions, access the associations within those Relationships directly, and register listeners for when they change.

Interfaces

There is one interface, Relationships, within the relationships module.

Relationships

A Relationships object lets you associate a Row in a one Table with the Id of a Row in another Table.

This is useful for creating parent-child relationships between the data in different Table objects, but it can also be used to model a linked list of Row objects in the same Table.

Create a Relationships object easily with the createRelationships function. From there, you can add new Relationship definitions (with the setRelationshipDefinition method), query their contents (with the getRemoteRowId method, the getLocalRowIds method, and the getLinkedRowIds method), and add listeners for when they change (with the addRemoteRowIdListener method, the addLocalRowIdsListener method, and the addLinkedRowIdsListener method).

This module defaults to creating relationships between Row objects by using one of their Cell values. However, far more complex relationships can be configured with a custom function.

Example

This example shows a very simple lifecycle of a Relationships object: from creation, to adding definitions (both local/remote table and linked list), getting their contents, and then registering and removing listeners for them.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog', next: 'felix'},
    felix: {species: 'cat', next: 'cujo'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
// A local/remote table relationship:
relationships.setRelationshipDefinition(
  'petSpecies', //  relationshipId
  'pets', //        localTableId to link from
  'species', //     remoteTableId to link to
  'species', //     cellId containing remote key
);
// A linked list relationship:
relationships.setRelationshipDefinition(
  'petSequence', // relationshipId
  'pets', //        localTableId to link from
  'pets', //        the same remoteTableId to link within
  'next', //        cellId containing link key
);

console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']

const listenerId1 = relationships.addLocalRowIdsListener(
  'petSpecies',
  'dog',
  () => {
    console.log('petSpecies relationship (to dog) changed');
    console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
  },
);
const listenerId2 = relationships.addLinkedRowIdsListener(
  'petSequence',
  'fido',
  () => {
    console.log('petSequence linked list (from fido) changed');
    console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
  },
);

store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'cujo', 'toto']

store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'petSequence linked list (from fido) changed'
// -> ['fido', 'felix', 'cujo', 'toto']

relationships.delListener(listenerId1);
relationships.delListener(listenerId2);
relationships.destroy();
See also

Relationships And Checkpoints guides

TinyDraw demo

Getter methods

This is the collection of getter methods within the Relationships interface. There are 8 getter methods in total.

getStore

The getStore method returns a reference to the underlying Store that is backing this Relationships object.

getStore(): Store
returnsStore

A reference to the Store.

Example

This example creates a Relationships object against a newly-created Store and then gets its reference in order to update its data.

const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);
relationships.getStore().setCell('pets', 'fido', 'species', 'dog');
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
getLocalTableId

The getLocalTableId method returns the Id of the underlying local Table that is used in the Relationship.

getLocalTableId(relationshipId: string): string
TypeDescription
relationshipIdstring

The Id of a Relationship.

returnsstring

The Id of the local Table backing the Relationship, or `undefined`.

If the Relationship Id is invalid, the method returns undefined.

Example

This example creates a Relationship object, a single Relationship definition, and then queries it (and a non-existent definition) to get the underlying local Table Id.

const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

console.log(relationships.getLocalTableId('petSpecies'));
// -> 'pets'
console.log(relationships.getLocalTableId('petColor'));
// -> undefined
getRemoteTableId

The getRemoteTableId method returns the Id of the underlying remote Table that is used in the Relationship.

getRemoteTableId(relationshipId: string): string
TypeDescription
relationshipIdstring

The Id of a Relationship.

returnsstring

The Id of the remote Table backing the Relationship, or `undefined`.

If the Relationship Id is invalid, the method returns undefined.

Example

This example creates a Relationship object, a single Relationship definition, and then queries it (and a non-existent definition) to get the underlying remote Table Id.

const relationships = createRelationships(createStore());
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

console.log(relationships.getRemoteTableId('petSpecies'));
// -> 'species'
console.log(relationships.getRemoteTableId('petColor'));
// -> undefined
getLinkedRowIds

The getLinkedRowIds method gets the linked Row Ids for a given Row in a linked list Relationship.

getLinkedRowIds(
  relationshipId: string,
  firstRowId: string,
): Ids
TypeDescription
relationshipIdstring

The Id of the Relationship.

firstRowIdstring

The Id of the first Row in the linked list Relationship.

returnsIds

The linked Row Ids in the Relationship.

A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.

If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then an array containing just the first Row Id is returned.

Example

This example creates a Store, creates a Relationships object, and defines a simple linked list Relationship. It then uses getLinkedRowIds to see the linked Row Ids in the Relationship (and also the linked Row Ids for a Row that does not exist, and for a Relationship that has not been defined).

const store = createStore().setTable('pets', {
  fido: {species: 'dog', next: 'felix'},
  felix: {species: 'cat', next: 'cujo'},
  cujo: {species: 'dog'},
});

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSequence',
  'pets',
  'pets',
  'next',
);

console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'felix'));
// -> ['felix', 'cujo']
console.log(relationships.getLinkedRowIds('petSequence', 'toto'));
// -> ['toto']
console.log(relationships.getLinkedRowIds('petFriendships', 'fido'));
// -> ['fido']
getLocalRowIds

The getLocalRowIds method gets the local Row Ids for a given remote Row in a Relationship.

getLocalRowIds(
  relationshipId: string,
  remoteRowId: string,
): Ids
TypeDescription
relationshipIdstring

The Id of the Relationship.

remoteRowIdstring

The Id of the remote Row in the Relationship.

returnsIds

The local Row Ids in the Relationship, or an empty array.

If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then an empty array is returned.

Example

This example creates a Store, creates a Relationships object, and defines a simple Relationship. It then uses getLocalRowIds to see the local Row Ids in the Relationship (and also the local Row Ids for a remote Row that does not exist, and for a Relationship that has not been defined).

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
console.log(relationships.getLocalRowIds('petSpecies', 'worm'));
// -> []
console.log(relationships.getLocalRowIds('petColor', 'brown'));
// -> []
getRemoteRowId

The getRemoteRowId method gets the remote Row Id for a given local Row in a Relationship.

getRemoteRowId(
  relationshipId: string,
  localRowId: string,
): undefined | string
TypeDescription
relationshipIdstring

The Id of the Relationship.

localRowIdstring

The Id of the local Row in the Relationship.

returnsundefined | string

The remote Row Id in the Relationship, or `undefined`.

If the identified Relationship or Row does not exist (or if the definition references a Table that does not exist) then undefined is returned.

Example

This example creates a Store, creates a Relationships object, and defines a simple Relationship. It then uses getRemoteRowId to see the remote Row Id in the Relationship (and also the remote Row Ids for a local Row that does not exist, and for a Relationship that has not been defined).

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getRemoteRowId('petSpecies', 'toto'));
// -> undefined
console.log(relationships.getRemoteRowId('petColor', 'fido'));
// -> undefined
getRelationshipIds

The getRelationshipIds method returns an array of the Relationship Ids registered with this Relationships object.

getRelationshipIds(): Ids
returnsIds

An array of Ids.

Example

This example creates a Relationships object with two definitions, and then gets the Ids of the definitions.

const relationships = createRelationships(createStore())
  .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
  .setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');
console.log(relationships.getRelationshipIds());
// -> ['petSpecies', 'petSequence']
hasRelationship

The hasRelationship method returns a boolean indicating whether a given Relationship exists in the Relationships object.

hasRelationship(indexId: string): boolean
TypeDescription
indexIdstring
returnsboolean

Whether a Relationship with that Id exists.

Example

This example shows two simple Relationship existence checks.

const relationships = createRelationships(
  createStore(),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');
console.log(relationships.hasRelationship('petSpecies'));
// -> true
console.log(relationships.hasRelationship('petColor'));
// -> false
Listener methods

This is the collection of listener methods within the Relationships interface. There are 4 listener methods in total.

addLinkedRowIdsListener

The addLinkedRowIdsListener method registers a listener function with the Relationships object that will be called whenever the linked Row Ids in a linked list Relationship change.

addLinkedRowIdsListener(
  relationshipId: string,
  firstRowId: string,
  listener: LinkedRowIdsListener,
): string
TypeDescription
relationshipIdstring

The Id of the Relationship to listen to.

firstRowIdstring

The Id of the first Row of the linked list to listen to.

listenerLinkedRowIdsListener

The function that will be called whenever the linked Row Ids change.

returnsstring

A unique Id for the listener that can later be used to remove it.

A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.

You listen to changes to a linked list starting from a single first Row by specifying the Relationship Id and local Row Id as the method's first two parameters.

Unlike other listener registration methods, you cannot provide null wildcards for the first two parameters of the addLinkedRowIdsListener method. This prevents the prohibitive expense of tracking all the possible linked lists (and partial linked lists within them) in a Store.

The provided listener is a LinkedRowIdsListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the first Row that had its linked list change.

Example

This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific first Row's linked Row objects.

const store = createStore().setTable('pets', {
  fido: {species: 'dog', next: 'felix'},
  felix: {species: 'cat', next: 'cujo'},
  cujo: {species: 'dog'},
});

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSequence',
  'pets',
  'pets',
  'next',
);

const listenerId = relationships.addLinkedRowIdsListener(
  'petSequence',
  'fido',
  (relationships, relationshipId, firstRowId) => {
    console.log('petSequence linked list (from fido) changed');
    console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
  },
);

store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'petSequence linked list (from fido) changed'
// -> ['fido', 'felix', 'cujo', 'toto']

relationships.delListener(listenerId);
addLocalRowIdsListener

The addLocalRowIdsListener method registers a listener function with the Relationships object that will be called whenever the local Row Ids in a Relationship change.

addLocalRowIdsListener(
  relationshipId: IdOrNull,
  remoteRowId: IdOrNull,
  listener: LocalRowIdsListener,
): string
TypeDescription
relationshipIdIdOrNull

The Id of the Relationship to listen to, or null as a wildcard.

remoteRowIdIdOrNull

The Id of the remote Row to listen to, or null as a wildcard.

listenerLocalRowIdsListener

The function that will be called whenever the local Row Ids change.

returnsstring

A unique Id for the listener that can later be used to remove it.

You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).

Both, either, or neither of the relationshipId and remoteRowId parameters can be wildcarded with null. You can listen to a specific remote Row in a specific Relationship, any remote Row in a specific Relationship, a specific remote Row in any Relationship, or any remote Row in any Relationship.

The provided listener is a LocalRowIdsListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the remote Row that had its local Row objects change.

Examples

This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific remote Row's local Row objects.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    wolf: {price: 10},
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

const listenerId = relationships.addLocalRowIdsListener(
  'petSpecies',
  'dog',
  (relationships, relationshipId, remoteRowId) => {
    console.log('petSpecies relationship (to dog) changed');
    console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
  },
);

store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'cujo', 'toto']

relationships.delListener(listenerId);

This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to any remote Row's local Row objects.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog', color: 'brown'},
    felix: {species: 'cat', color: 'black'},
    cujo: {species: 'dog', color: 'brown'},
    toto: {species: 'dog', color: 'grey'},
  })
  .setTable('species', {
    wolf: {price: 10},
    dog: {price: 5},
    cat: {price: 4},
  })
  .setTable('color', {
    brown: {discount: 0.1},
    black: {discount: 0},
    grey: {discount: 0.2},
  });

const relationships = createRelationships(store)
  .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
  .setRelationshipDefinition('petColor', 'pets', 'color', 'color');

const listenerId = relationships.addLocalRowIdsListener(
  null,
  null,
  (relationships, relationshipId, remoteRowId) => {
    console.log(
      `${relationshipId} relationship (to ${remoteRowId}) changed`,
    );
    console.log(relationships.getLocalRowIds(relationshipId, remoteRowId));
  },
);

store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'});
// -> 'petSpecies relationship (to dog) changed'
// -> ['fido', 'toto']
// -> 'petSpecies relationship (to wolf) changed'
// -> ['cujo']
// -> 'petColor relationship (to brown) changed'
// -> ['fido']
// -> 'petColor relationship (to grey) changed'
// -> ['toto', 'cujo']

relationships.delListener(listenerId);
addRemoteRowIdListener

The addRemoteRowIdListener method registers a listener function with the Relationships object that will be called whenever a remote Row Id in a Relationship changes.

addRemoteRowIdListener(
  relationshipId: IdOrNull,
  localRowId: IdOrNull,
  listener: RemoteRowIdListener,
): string
TypeDescription
relationshipIdIdOrNull

The Id of the Relationship to listen to, or null as a wildcard.

localRowIdIdOrNull

The Id of the local Row to listen to, or null as a wildcard.

listenerRemoteRowIdListener

The function that will be called whenever the remote Row Id changes.

returnsstring

A unique Id for the listener that can later be used to remove it.

You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).

Both, either, or neither of the relationshipId and localRowId parameters can be wildcarded with null. You can listen to a specific local Row in a specific Relationship, any local Row in a specific Relationship, a specific local Row in any Relationship, or any local Row in any Relationship.

The provided listener is a RemoteRowIdListener function, and will be called with a reference to the Relationships object, the Id of the Relationship, and the Id of the local Row that had its remote Row change.

Examples

This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to a specific local Row's remote Row.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    wolf: {price: 10},
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

const listenerId = relationships.addRemoteRowIdListener(
  'petSpecies',
  'cujo',
  (relationships, relationshipId, localRowId) => {
    console.log('petSpecies relationship (from cujo) changed');
    console.log(relationships.getRemoteRowId('petSpecies', 'cujo'));
  },
);

store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'petSpecies relationship (from cujo) changed'
// -> 'wolf'

relationships.delListener(listenerId);

This example creates a Store, a Relationships object, and then registers a listener that responds to any changes to any local Row's remote Row. It also illustrates how you can use the getStore method and the getRemoteRowId method to resolve the remote Row as a whole.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog', color: 'brown'},
    felix: {species: 'cat', color: 'black'},
    cujo: {species: 'dog', color: 'brown'},
  })
  .setTable('species', {
    wolf: {price: 10},
    dog: {price: 5},
    cat: {price: 4},
  })
  .setTable('color', {
    brown: {discount: 0.1},
    black: {discount: 0},
    grey: {discount: 0.2},
  });

const relationships = createRelationships(store)
  .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
  .setRelationshipDefinition('petColor', 'pets', 'color', 'color');

const listenerId = relationships.addRemoteRowIdListener(
  null,
  null,
  (relationships, relationshipId, localRowId) => {
    console.log(
      `${relationshipId} relationship (from ${localRowId}) changed`,
    );
    console.log(relationships.getRemoteRowId(relationshipId, localRowId));
    console.log(
      relationships
        .getStore()
        .getRow(
          relationships.getRemoteTableId(relationshipId),
          relationships.getRemoteRowId(relationshipId, localRowId),
        ),
    );
  },
);

store.setRow('pets', 'cujo', {species: 'wolf', color: 'grey'});
// -> 'petSpecies relationship (from cujo) changed'
// -> 'wolf'
// -> {price: 10}
// -> 'petColor relationship (from cujo) changed'
// -> 'grey'
// -> {discount: 0.2}

relationships.delListener(listenerId);
delListener

The delListener method removes a listener that was previously added to the Relationships object.

delListener(listenerId: string): Relationships
TypeDescription
listenerIdstring

The Id of the listener to remove.

returnsRelationships

A reference to the Relationships object.

Use the Id returned by whichever method was used to add the listener. Note that the Relationships object may re-use this Id for future listeners added to it.

Example

This example creates a Store, a Relationships object, registers a listener, and then removes it.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    wolf: {price: 10},
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

const listenerId = relationships.addLocalRowIdsListener(
  'petSpecies',
  'dog',
  (relationships, relationshipId, remoteRowId) => {
    console.log('petSpecies relationship (to dog) changed');
  },
);

store.setRow('pets', 'toto', {species: 'dog'});
// -> 'petSpecies relationship (to dog) changed'

relationships.delListener(listenerId);

store.setRow('pets', 'toto', {species: 'dog'});
// -> undefined
// The listener is not called.
Configuration methods

This is the collection of configuration methods within the Relationships interface. There are only two configuration methods, delRelationshipDefinition and setRelationshipDefinition.

delRelationshipDefinition

The delRelationshipDefinition method removes an existing Relationship definition.

delRelationshipDefinition(relationshipId: string): Relationships
TypeDescription
relationshipIdstring

The Id of the Relationship to remove.

returnsRelationships

A reference to the Relationships object.

Example

This example creates a Store, creates a Relationships object, defines a simple Relationship, and then removes it.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);
console.log(relationships.getRelationshipIds());
// -> ['petSpecies']

relationships.delRelationshipDefinition('petSpecies');
console.log(relationships.getRelationshipIds());
// -> []
setRelationshipDefinition

The setRelationshipDefinition method lets you set the definition of a Relationship.

setRelationshipDefinition(
  relationshipId: string,
  localTableId: string,
  remoteTableId: string,
  getRemoteRowId: string | (getCell: GetCell, localRowId: string) => string,
): Relationships
TypeDescription
relationshipIdstring

The Id of the Relationship to define.

localTableIdstring

The Id of the local Table for the Relationship.

remoteTableIdstring

The Id of the remote Table for the Relationship (or the same as the localTableId in the case of a linked list).

getRemoteRowIdstring | (getCell: GetCell, localRowId: string) => string

Either the Id of the Cell containing, or a function that produces, the Id that is used to indicate which Row in the remote Table a local Row is related to.

returnsRelationships

A reference to the Relationships object.

Every Relationship definition is identified by a unique Id, and if you re-use an existing Id with this method, the previous definition is overwritten.

An Relationship is based on connections between Row objects, often in two different Table objects. Therefore the definition requires the localTableId parameter to specify the 'local' Table to create the Relationship from, and the remoteTableId parameter to specify the 'remote' Table to create Relationship to.

A linked list Relationship is one that has the same Table specified as both local Table Id and remote Table Id, allowing you to create a sequence of Row objects within that one Table.

A local Row is related to a remote Row by specifying which of its (local) Cell values contains the (remote) Row Id, using the getRemoteRowId parameter. Alternatively, a custom function can be provided that produces your own remote Row Id from the local Row as a whole.

Examples

This example creates a Store, creates a Relationships object, and defines a simple Relationship based on the values in the species Cell of the pets Table that relates a Row to another in the species Table.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies', // relationshipId
  'pets', //       localTableId to link from
  'species', //    remoteTableId to link to
  'species', //    cellId containing remote key
);

console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']

This example creates a Store, creates a Relationships object, and defines a linked list Relationship based on the values in the next Cell of the pets Table that relates a Row to another in the same Table.

const store = createStore().setTable('pets', {
  fido: {species: 'dog', next: 'felix'},
  felix: {species: 'cat', next: 'cujo'},
  cujo: {species: 'dog'},
});

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSequence', // relationshipId
  'pets', //        localTableId to link from
  'pets', //        the same remoteTableId to link within
  'next', //        cellId containing link key
);

console.log(relationships.getLinkedRowIds('petSequence', 'fido'));
// -> ['fido', 'felix', 'cujo']
Iterator methods

This is the collection of iterator methods within the Relationships interface. There is only one method, forEachRelationship.

forEachRelationship

The forEachRelationship method takes a function that it will then call for each Relationship in a specified Relationships object.

forEachRelationship(relationshipCallback: RelationshipCallback): void
TypeDescription
relationshipCallbackRelationshipCallback

The function that should be called for every Relationship.

returnsvoid

This has no return value.

This method is useful for iterating over the structure of the Relationships object in a functional style. The relationshipCallback parameter is a RelationshipCallback function that will be called with the Id of each Relationship, and with a function that can then be used to iterate over each local Row involved in the Relationship.

Example

This example iterates over each Relationship in a Relationships object, and lists each Row Id within them.

const store = createStore().setTable('pets', {
  fido: {species: 'dog', next: 'felix'},
  felix: {species: 'cat', next: 'cujo'},
  cujo: {species: 'dog'},
});
const relationships = createRelationships(store)
  .setRelationshipDefinition('petSpecies', 'pets', 'species', 'species')
  .setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');

relationships.forEachRelationship((relationshipId, forEachRow) => {
  console.log(relationshipId);
  forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'petSpecies'
// -> '- fido'
// -> '- felix'
// -> '- cujo'
// -> 'petSequence'
// -> '- fido'
// -> '- felix'
// -> '- cujo'
Lifecycle methods

This is the collection of lifecycle methods within the Relationships interface. There is only one method, destroy.

destroy

The destroy method should be called when this Relationships object is no longer used.

destroy(): void

This guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.

Example

This example creates a Store, adds an Relationships object with a definition (that registers a RowListener with the underlying Store), and then destroys it again, removing the listener.

const store = createStore()
  .setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  })
  .setTable('species', {
    wolf: {price: 10},
    dog: {price: 5},
    cat: {price: 4},
  });

const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);
console.log(store.getListenerStats().row);
// -> 1

relationships.destroy();

console.log(store.getListenerStats().row);
// -> 0
Development methods

This is the collection of development methods within the Relationships interface. There is only one method, getListenerStats.

getListenerStats

The getListenerStats method provides a set of statistics about the listeners registered with the Relationships object, and is used for debugging purposes.

getListenerStats(): RelationshipsListenerStats
returnsRelationshipsListenerStats

A RelationshipsListenerStats object containing Relationships listener statistics.

The RelationshipsListenerStats object contains a breakdown of the different types of listener.

The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.

Example

This example gets the listener statistics of a Relationships object.

const store = createStore();
const relationships = createRelationships(store);
relationships.addRemoteRowIdListener(null, null, () => {
  console.log('Remote Row Id changed');
});
relationships.addLocalRowIdsListener(null, null, () => {
  console.log('Local Row Id changed');
});

const listenerStats = relationships.getListenerStats();
console.log(listenerStats.remoteRowId);
// -> 1
console.log(listenerStats.localRowIds);
// -> 1

Functions

There is one function, createRelationships, within the relationships module.

createRelationships

The createRelationships function creates an Relationships object, and is the main entry point into the relationships module.

createRelationships(store: Store): Relationships
TypeDescription
storeStore

The Store for which to register Relationships.

returnsRelationships

A reference to the new Relationships object.

It is trivially simple.

A given Store can only have one Relationships object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Relationships object created by the first.

Examples

This example creates an Relationships object.

const store = createStore();
const relationships = createRelationships(store);
console.log(relationships.getRelationshipIds());
// -> []

This example creates a Relationships object, and calls the method a second time for the same Store to return the same object.

const store = createStore();
const relationships1 = createRelationships(store);
const relationships2 = createRelationships(store);
console.log(relationships1 === relationships2);
// -> true

Type aliases

These are the type aliases within the relationships module.

Listener type aliases

This is the collection of listener type aliases within the relationships module. There are only three listener type aliases, LinkedRowIdsListener, LocalRowIdsListener, and RemoteRowIdListener.

LinkedRowIdsListener

The LinkedRowIdsListener type describes a function that is used to listen to changes to the local Row Id ends of a Relationship.

(
  relationships: Relationships,
  relationshipId: Id,
  firstRowId: Id,
): void
TypeDescription
relationshipsRelationships

A reference to the Relationships object that changed.

relationshipIdId

The Id of the Relationship that changed.

firstRowIdId

The Id of the first Row of the the linked list whose members changed.

returnsvoid

This has no return value.

A LinkedRowIdsListener is provided when using the addLinkedRowIdsListener method. See that method for specific examples.

When called, a LinkedRowIdsListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the first Row of the the linked list whose members changed.

LocalRowIdsListener

The LocalRowIdsListener type describes a function that is used to listen to changes to the local Row Id ends of a Relationship.

(
  relationships: Relationships,
  relationshipId: Id,
  remoteRowId: Id,
): void
TypeDescription
relationshipsRelationships

A reference to the Relationships object that changed.

relationshipIdId

The Id of the Relationship that changed.

remoteRowIdId

The Id of the remote Row whose local Row Ids changed.

returnsvoid

This has no return value.

A LocalRowIdsListener is provided when using the addLocalRowIdsListener method. See that method for specific examples.

When called, a LocalRowIdsListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the remote Row whose local Row Ids changed.

RemoteRowIdListener

The RemoteRowIdListener type describes a function that is used to listen to changes to the remote Row Id end of a Relationship.

(
  relationships: Relationships,
  relationshipId: Id,
  localRowId: Id,
): void
TypeDescription
relationshipsRelationships

A reference to the Relationships object that changed.

relationshipIdId

The Id of the Relationship that changed.

localRowIdId

The Id of the local Row whose remote Row Id changed.

returnsvoid

This has no return value.

A RemoteRowIdListener is provided when using the addRemoteRowIdListener method. See that method for specific examples.

When called, a RemoteRowIdListener is given a reference to the Relationships object, the Id of the Relationship that changed, and the Id of the local Row whose remote Row Id changed.

Callback type aliases

This is the collection of callback type aliases within the relationships module. There is only one type alias, RelationshipCallback.

RelationshipCallback

The RelationshipCallback type describes a function that takes a Relationship's Id and a callback to loop over each local Row within it.

(
  relationshipId: Id,
  forEachRow: (rowCallback: RowCallback) => void,
): void
TypeDescription
relationshipIdId

The Id of the Relationship that the callback can operate on.

forEachRow(rowCallback: RowCallback) => void

A function that will let you iterate over the local Row objects in this Relationship.

returnsvoid

This has no return value.

A RelationshipCallback is provided when using the forEachRelationship method, so that you can do something based on every Relationship in the Relationships object. See that method for specific examples.

Concept type aliases

This is the collection of concept type aliases within the relationships module. There is only one type alias, Relationship.

Relationship

The Relationship type represents the concept of a map that connects one Row object to another, often in another Table.

{
  remoteRowId: {[localRowId: Id]: Id};
  localRowIds: {[remoteRowId: Id]: Ids};
  linkedRowIds: {[firstRowId: Id]: Ids};
}
TypeDescription
remoteRowId{[localRowId: Id]: Id}
localRowIds{[remoteRowId: Id]: Ids}
linkedRowIds{[firstRowId: Id]: Ids}

The Relationship has a one-to-many nature. One local Row Id is linked to one remote Row Id (in the remote Table), as described by the setRelationshipDefinition method - and one remote Row Id may map back to multiple local Row Ids (in the local Table).

A Relationship where the local Table is the same as the remote Table can be used to model a 'linked list', where Row A references Row B, Row B references Row C, and so on.

Note that the Relationship type is not actually used in the API, and you instead enumerate and access its structure with the getRemoteRowId method, the getLocalRowIds method, and the getLinkedRowIds method.

Development type aliases

This is the collection of development type aliases within the relationships module. There is only one type alias, RelationshipsListenerStats.

RelationshipsListenerStats

The RelationshipsListenerStats type describes the number of listeners registered with the Relationships object, and can be used for debugging purposes.

{
  remoteRowId?: number;
  localRowIds?: number;
  linkedRowIds?: number;
}
TypeDescription
remoteRowId?number

The number of RemoteRowIdListeners registered with the Relationships object.

localRowIds?number

The number of LocalRowIdsListeners registered with the Relationships object.

linkedRowIds?number

The number of LinkedRowIds registered with the Relationships object.

A RelationshipsListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.

checkpoints

The checkpoints module of the TinyBase project provides the ability to create and track checkpoints made to the data in Store objects.

The main entry point to this module is the createCheckpoints function, which returns a new Checkpoints object. From there, you can create new checkpoints, go forwards or backwards to others, and register listeners for when the list of checkpoints change.

Interfaces

There is one interface, Checkpoints, within the checkpoints module.

Checkpoints

A Checkpoints object lets you set checkpoints on a Store, and move forward and backward through them to create undo and redo functionality.

Create a Checkpoints object easily with the createCheckpoints function. From there, you can set checkpoints (with the addCheckpoint method), query the checkpoints available (with the getCheckpointIds method), move forward and backward through them (with the goBackward method, goForward method, and goTo method), and add listeners for when the list checkpoints changes (with the addCheckpointIdsListener method).

Every checkpoint can be given a label which can be used to describe the actions that changed the Store before this checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.

You

Example

This example shows a simple lifecycle of a Checkpoints object: from creation, to adding a checkpoint, getting the list of available checkpoints, and then registering and removing a listener for them.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
checkpoints.setSize(200);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]

checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]

const listenerId = checkpoints.addCheckpointIdsListener(() => {
  console.log(checkpoints.getCheckpointIds());
});
store.setCell('pets', 'fido', 'species', 'dog');
// -> [['0'], undefined, []]
checkpoints.addCheckpoint();
// -> [['0'], '2', []]
// Previous redo of checkpoint '1' is now not possible.

checkpoints.delListener(listenerId);
checkpoints.destroy();
See also

Relationships And Checkpoints guides

Todo App demos

TinyDraw demo

Getter methods

This is the collection of getter methods within the Checkpoints interface. There are 4 getter methods in total.

getStore

The getStore method returns a reference to the underlying Store that is backing this Checkpoints object.

getStore(): Store
returnsStore

A reference to the Store.

Example

This example creates a Checkpoints object against a newly-created Store and then gets its reference in order to update its data and set a checkpoint.

const checkpoints = createCheckpoints(createStore());
checkpoints.getStore().setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
getCheckpoint

The getCheckpoint method fetches the label for a checkpoint, if it had been provided at the time of the addCheckpoint method or set subsequently with the setCheckpoint method.

getCheckpoint(checkpointId: string): undefined | string
TypeDescription
checkpointIdstring

The Id of the checkpoint to get the label for.

returnsundefined | string

A string label for the requested checkpoint, an empty string if it was never set, or `undefined` if the checkpoint does not exist.

If the checkpoint has had no label provided, this method will return an empty string.

Examples

This example creates a Store, adds a Checkpoints object, and sets a checkpoint with a label, before retrieving it again.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
console.log(checkpoints.addCheckpoint('sale'));
// -> '1'

console.log(checkpoints.getCheckpoint('1'));
// -> 'sale'

This example creates a Store, adds a Checkpoints object, and sets a checkpoint without a label, setting it subsequently. A non-existent checkpoint return an undefined label.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpoint('1'));
// -> ''

checkpoints.setCheckpoint('1', 'sold');
console.log(checkpoints.getCheckpoint('1'));
// -> 'sold'

console.log(checkpoints.getCheckpoint('2'));
// -> undefined
getCheckpointIds

The getCheckpointIds method returns an array of the checkpoint Ids being managed by this Checkpoints object.

getCheckpointIds(): CheckpointIds
returnsCheckpointIds

A CheckpointIds array, containing the checkpoint Ids managed by this Checkpoints object.

The returned CheckpointIds array contains 'backward' checkpoint Ids, the current checkpoint Id (if present), and the 'forward' checkpointIds. Together, these are sufficient to understand the state of the Checkpoints object and what movement is possible backward or forward through the checkpoint stack.

Example

This example creates a Store, adds a Checkpoints object, and then gets the Ids of the checkpoints as it sets them and moves around the stack.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]

checkpoints.goBackward();
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]

checkpoints.goForward();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]
hasCheckpoint

The hasCheckpoint method returns a boolean indicating whether a given Checkpoint exists in the Checkpoints object.

hasCheckpoint(checkpointId: string): boolean
TypeDescription
checkpointIdstring

The Id of a possible Checkpoint in the Checkpoints object.

returnsboolean

Whether a Checkpoint with that Id exists.

Example

This example shows two simple Checkpoint existence checks.

const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
console.log(checkpoints.hasCheckpoint('0'));
// -> true
console.log(checkpoints.hasCheckpoint('1'));
// -> false
Setter methods

This is the collection of setter methods within the Checkpoints interface. There are only two setter methods, addCheckpoint and setCheckpoint.

addCheckpoint

The addCheckpoint method records a checkpoint of the Store into the Checkpoints object that can be reverted to in the future.

addCheckpoint(label?: string): string
TypeDescription
label?string

An optional label to describe the actions leading up to this checkpoint.

returnsstring

The Id of the newly-created checkpoint.

If no changes have been made to the Store since the last time a checkpoint was made, this method will have no effect.

The optional label parameter can be used to describe the actions that changed the Store before this checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.

Example

This example creates a Store, adds a Checkpoints object, and adds two checkpoints, one with a label.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'species', 'dog');
const checkpointId1 = checkpoints.addCheckpoint();
console.log(checkpointId1);
// -> '1'

console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]

console.log(checkpoints.getCheckpoint('2'));
// -> 'sale'
setCheckpoint

The setCheckpoint method updates the label for a checkpoint in the Checkpoints object after it has been created

setCheckpoint(
  checkpointId: string,
  label: string,
): Checkpoints
TypeDescription
checkpointIdstring

The Id of the checkpoint to set the label for.

labelstring

A label to describe the actions leading up to this checkpoint or left undefined if you want to clear the current label.

returnsCheckpoints

A reference to the Checkpoints object.

The label parameter can be used to describe the actions that changed the Store before the given checkpoint. This can be useful for interfaces that let users 'Undo [last action]'.

Generally you will provide the label parameter when the addCheckpoint method is called. Use this setCheckpoint method only when you need to change the label at a later point.

You cannot add a label to a checkpoint that does not yet exist.

Example

This example creates a Store, adds a Checkpoints object, and sets two checkpoints, one with a label, which are both then re-labelled.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint();
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');

console.log(checkpoints.getCheckpoint('1'));
// -> ''
console.log(checkpoints.getCheckpoint('2'));
// -> 'sale'

checkpoints.setCheckpoint('1', 'identified');
checkpoints.setCheckpoint('2', '');

console.log(checkpoints.getCheckpoint('1'));
// -> 'identified'
console.log(checkpoints.getCheckpoint('2'));
// -> ''

checkpoints.setCheckpoint('3', 'unknown');
console.log(checkpoints.getCheckpoint('3'));
// -> undefined
Listener methods

This is the collection of listener methods within the Checkpoints interface. There are only three listener methods, addCheckpointIdsListener, addCheckpointListener, and delListener.

addCheckpointIdsListener

The addCheckpointIdsListener method registers a listener function with the Checkpoints object that will be called whenever its set of checkpoints changes.

addCheckpointIdsListener(listener: CheckpointIdsListener): string
TypeDescription
listenerCheckpointIdsListener

The function that will be called whenever the checkpoints change.

returnsstring

A unique Id for the listener that can later be used to remove it.

The provided listener is a CheckpointIdsListener function, and will be called with a reference to the Checkpoints object.

Example

This example creates a Store, a Checkpoints object, and then registers a listener that responds to any changes to the checkpoints.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

const listenerId = checkpoints.addCheckpointIdsListener(() => {
  console.log('Checkpoint Ids changed');
  console.log(checkpoints.getCheckpointIds());
});

store.setCell('pets', 'fido', 'species', 'dog');
// -> 'Checkpoint Ids changed'
// -> [['0'], undefined, []]

checkpoints.addCheckpoint();
// -> 'Checkpoint Ids changed'
// -> [['0'], '1', []]

checkpoints.goBackward();
// -> 'Checkpoint Ids changed'
// -> [[], '0', ['1']]

checkpoints.goForward();
// -> 'Checkpoint Ids changed'
// -> [['0'], '1', []]

checkpoints.delListener(listenerId);
checkpoints.destroy();
addCheckpointListener

The addCheckpointListener method registers a listener function with the Checkpoints object that will be called whenever the label of a checkpoint changes.

addCheckpointListener(
  checkpointId: IdOrNull,
  listener: CheckpointListener,
): string
TypeDescription
checkpointIdIdOrNull

The Id of the checkpoint to listen to, or null as a wildcard.

listenerCheckpointListener

The function that will be called whenever the checkpoint label changes.

returnsstring

A unique Id for the listener that can later be used to remove it.

You can either listen to a single checkpoint label (by specifying the checkpoint Id as the method's first parameter), or changes to any checkpoint label (by providing a null wildcard).

The provided listener is a CheckpointListener function, and will be called with a reference to the Checkpoints object, and the Id of the checkpoint whose label changed.

Example

This example creates a Store, a Checkpoints object, and then registers a listener that responds to any changes to a specific checkpoint label, including when the checkpoint no longer exists.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

const listenerId = checkpoints.addCheckpointListener('1', () => {
  console.log('Checkpoint 1 label changed');
  console.log(checkpoints.getCheckpoint('1'));
});

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
// -> 'Checkpoint 1 label changed'
// -> 'sale'

checkpoints.setCheckpoint('1', 'sold');
// -> 'Checkpoint 1 label changed'
// -> 'sold'

checkpoints.setCheckpoint('1', 'sold');
// The listener is not called when the label does not change.

checkpoints.goTo('0');
store.setCell('pets', 'fido', 'sold', false);
// -> 'Checkpoint 1 label changed'
// -> undefined
// The checkpoint no longer exists.

checkpoints.delListener(listenerId);
checkpoints.destroy();
delListener

The delListener method removes a listener that was previously added to the Checkpoints object.

delListener(listenerId: string): Checkpoints
TypeDescription
listenerIdstring

The Id of the listener to remove.

returnsCheckpoints

A reference to the Checkpoints object.

Use the Id returned by the addCheckpointIdsListener method. Note that the Checkpoints object may re-use this Id for future listeners added to it.

Example

This example creates a Store, a Checkpoints object, registers a listener, and then removes it.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

const listenerId = checkpoints.addCheckpointIdsListener(() => {
  console.log('checkpoints changed');
});

store.setCell('pets', 'fido', 'species', 'dog');
// -> 'checkpoints changed'

checkpoints.addCheckpoint();
// -> 'checkpoints changed'

checkpoints.delListener(listenerId);

store.setCell('pets', 'fido', 'sold', 'true');
// -> undefined
// The listener is not called.
Configuration methods

This is the collection of configuration methods within the Checkpoints interface. There is only one method, setSize.

setSize

The setSize method lets you specify how many checkpoints the Checkpoints object will store.

setSize(size: number): Checkpoints
TypeDescription
sizenumber

The number of checkpoints that this Checkpoints object should hold.

returnsCheckpoints

A reference to the Checkpoints object.

If you set more checkpoints than this size, the oldest checkpoints will be pruned to make room for more recent ones.

The default size for a newly-created Checkpoints object is 100.

Example

This example creates a Store, adds a Checkpoints object, reduces the size of the Checkpoints object dramatically and then creates more than that number of checkpoints to demonstrate the oldest being pruned.

const store = createStore().setTables({pets: {fido: {views: 0}}});

const checkpoints = createCheckpoints(store);
checkpoints.setSize(2);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'views', 1);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]

store.setCell('pets', 'fido', 'views', 2);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]

store.setCell('pets', 'fido', 'views', 3);
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [['1', '2'], '3', []]
Iterator methods

This is the collection of iterator methods within the Checkpoints interface. There is only one method, forEachCheckpoint.

forEachCheckpoint

The forEachCheckpoint method takes a function that it will then call for each Checkpoint in a specified Checkpoints object.

forEachCheckpoint(checkpointCallback: CheckpointCallback): void
TypeDescription
checkpointCallbackCheckpointCallback

The function that should be called for every Checkpoint.

returnsvoid

This has no return value.

This method is useful for iterating over the structure of the Checkpoints object in a functional style. The checkpointCallback parameter is a CheckpointCallback function that will be called with the Id of each Checkpoint.

Example

This example iterates over each Checkpoint in a Checkpoints object.

const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');

checkpoints.forEachCheckpoint((checkpointId, label) => {
  console.log(`${checkpointId}:${label}`);
});
// -> '0:'
// -> '1:sale'
Lifecycle methods

This is the collection of lifecycle methods within the Checkpoints interface. There are only two lifecycle methods, clear and destroy.

clear

The clear method resets this Checkpoints object to its initial state, removing all the checkpoints it has been managing.

clear(): Checkpoints
returnsCheckpoints

A reference to the Checkpoints object.

Obviously this method should be used with caution as it destroys the ability to undo recent changes to the Store (though of course the Store itself is not reset by this method).

This method can be useful when a Store is being loaded via a Persister asynchronously after the Checkpoints object has been attached, and you don't want users to be able to undo the initial load of the data. In this you could call the clear method immediately after the initial load so that that is the baseline from which all subsequent changes are tracked.

If you are listening to

Example

This example creates a Store, a Checkpoints object, adds a listener, makes a change and then clears the checkpoints.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

const listenerId = checkpoints.addCheckpointIdsListener(() => {
  console.log('checkpoints changed');
});

store.setCell('pets', 'fido', 'color', 'brown');
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'
store.setCell('pets', 'fido', 'sold', true);
// -> 'checkpoints changed'
checkpoints.addCheckpoint();
// -> 'checkpoints changed'

console.log(store.getTables());
// -> {pets: {fido: {sold: true, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]

checkpoints.clear();
// -> 'checkpoints changed'

console.log(store.getTables());
// -> {pets: {fido: {sold: true, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]
destroy

The destroy method should be called when this Checkpoints object is no longer used.

destroy(): void

This guarantees that all of the listeners that the object registered with the underlying Store are removed and it can be correctly garbage collected.

Example

This example creates a Store, adds a Checkpoints object (that registers a CellListener with the underlying Store), and then destroys it again, removing the listener.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(store.getListenerStats().cell);
// -> 1

checkpoints.destroy();

console.log(store.getListenerStats().cell);
// -> 0
Movement methods

This is the collection of movement methods within the Checkpoints interface. There are only three movement methods, goBackward, goForward, and goTo.

goBackward

The goBackward method moves the state of the underlying Store back to the previous checkpoint, effectively performing an 'undo' on the Store data.

goBackward(): Checkpoints
returnsCheckpoints

A reference to the Checkpoints object.

If there is no previous checkpoint to return to, this method has no effect.

Example

This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]

checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]
goForward

The goForward method moves the state of the underlying Store forwards to a future checkpoint, effectively performing an 'redo' on the Store data.

goForward(): Checkpoints
returnsCheckpoints

A reference to the Checkpoints object.

If there is no future checkpoint to return to, this method has no effect.

Note that if you have previously used the goBackward method to undo changes, the forwards 'redo' stack will only exist while you do not make changes to the Store. In general the goForward method is expected to be used to redo changes that were just undone.

Examples

This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change. It then goes forward again to restore the state with the changes.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]

checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]

checkpoints.goForward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> true
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]

This example creates a Store, a Checkpoints object, makes a change and then goes backward to the state of the Store before the change. It makes a new change, the redo stack disappears, and then the attempt to forward again has no effect.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', []]

checkpoints.goBackward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1']]

store.setCell('pets', 'fido', 'color', 'brown');
console.log(checkpoints.getCheckpointIds());
// -> [['0'], undefined, []]

checkpoints.goForward();
console.log(store.getCell('pets', 'fido', 'sold'));
// -> false
console.log(checkpoints.getCheckpointIds());
// -> [['0'], undefined, []]
// The original change cannot be redone.
goTo

The goTo method moves the state of the underlying Store backwards or forwards to a specified checkpoint.

goTo(checkpointId: string): Checkpoints
TypeDescription
checkpointIdstring

The Id of the checkpoint to move to.

returnsCheckpoints

A reference to the Checkpoints object.

If there is no checkpoint with the Id specified, this method has no effect.

Example

This example creates a Store, a Checkpoints object, makes two changes and then goes directly to the state of the Store before the two changes. It then goes forward again one change, also using the goTo method. Finally it tries to go to a checkpoint that does not exist.

const store = createStore().setTables({pets: {fido: {sold: false}}});

const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

store.setCell('pets', 'fido', 'color', 'brown');
checkpoints.addCheckpoint('identification');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(checkpoints.getCheckpointIds());
// -> [['0', '1'], '2', []]

checkpoints.goTo('0');
console.log(store.getTables());
// -> {pets: {fido: {sold: false}}}
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', ['1', '2']]

checkpoints.goTo('1');
console.log(store.getTables());
// -> {pets: {fido: {sold: false, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]

checkpoints.goTo('3');
console.log(store.getTables());
// -> {pets: {fido: {sold: false, color: 'brown'}}}
console.log(checkpoints.getCheckpointIds());
// -> [['0'], '1', ['2']]
Development methods

This is the collection of development methods within the Checkpoints interface. There is only one method, getListenerStats.

getListenerStats

The getListenerStats method provides a set of statistics about the listeners registered with the Checkpoints object, and is used for debugging purposes.

getListenerStats(): CheckpointsListenerStats
returnsCheckpointsListenerStats

A CheckpointsListenerStats object containing Checkpoints listener statistics.

The CheckpointsListenerStats object contains a breakdown of the different types of listener.

The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your application is not leaking listener registrations, for example.

Example

This example gets the listener statistics of a Checkpoints object.

const store = createStore();
const checkpoints = createCheckpoints(store);
checkpoints.addCheckpointIdsListener(() => {
  console.log('Checkpoint Ids changed');
});
checkpoints.addCheckpointListener(null, () => {
  console.log('Checkpoint label changed');
});

console.log(checkpoints.getListenerStats());
// -> {checkpointIds: 1, checkpoint: 1}

Functions

There is one function, createCheckpoints, within the checkpoints module.

createCheckpoints

The createCheckpoints function creates a Checkpoints object, and is the main entry point into the checkpoints module.

createCheckpoints(store: Store): Checkpoints
TypeDescription
storeStore

The Store for which to set Checkpoints.

returnsCheckpoints

A reference to the new Checkpoints object.

It is trivially simple.

A given Store can only have one Checkpoints object associated with it. If you call this function twice on the same Store, your second call will return a reference to the Checkpoints object created by the first.

Examples

This example creates a Checkpoints object.

const store = createStore();
const checkpoints = createCheckpoints(store);
console.log(checkpoints.getCheckpointIds());
// -> [[], '0', []]

This example creates a Checkpoints object, and calls the method a second time for the same Store to return the same object.

const store = createStore();
const checkpoints1 = createCheckpoints(store);
const checkpoints2 = createCheckpoints(store);
console.log(checkpoints1 === checkpoints2);
// -> true

Type aliases

These are the type aliases within the checkpoints module.

Listener type aliases

This is the collection of listener type aliases within the checkpoints module. There are only two listener type aliases, CheckpointIdsListener and CheckpointListener.

CheckpointIdsListener

The CheckpointIdsListener type describes a function that is used to listen to changes to the checkpoint Ids in a Checkpoints object.

(checkpoints: Checkpoints): void
TypeDescription
checkpointsCheckpoints

A reference to the Checkpoints object that changed.

returnsvoid

This has no return value.

A CheckpointIdsListener is provided when using the addCheckpointIdsListener method. See that method for specific examples.

When called, a CheckpointIdsListener is given a reference to the Checkpoints object.

CheckpointListener

The CheckpointListener type describes a function that is used to listen to changes to a checkpoint's label in a Checkpoints object.

(
  checkpoints: Checkpoints,
  checkpointId: Id,
): void
TypeDescription
checkpointsCheckpoints

A reference to the Checkpoints object that changed.

checkpointIdId

The Id of the checkpoint that changed.

returnsvoid

This has no return value.

A CheckpointListener is provided when using the addCheckpointListener method. See that method for specific examples.

When called, a CheckpointListener is given a reference to the Checkpoints object, and the Id of the checkpoint whose label changed.

Callback type aliases

This is the collection of callback type aliases within the checkpoints module. There is only one type alias, CheckpointCallback.

CheckpointCallback

The CheckpointCallback type describes a function that takes a Checkpoint's Id.

(
  checkpointId: Id,
  label?: string,
): void
TypeDescription
checkpointIdId

The Id of the Checkpoint that the callback can operate on.

label?string
returnsvoid

This has no return value.

A CheckpointCallback is provided when using the forEachCheckpoint method, so that you can do something based on every Checkpoint in the Checkpoints object. See that method for specific examples.

Identity type aliases

This is the collection of identity type aliases within the checkpoints module. There is only one type alias, CheckpointIds.

CheckpointIds

The CheckpointIds type is a representation of the list of checkpoint Ids stored in a Checkpoints object.

[Ids, Id | undefined, Ids]

There are three parts to a CheckpointsIds array:

  • The 'backward' checkpoint Ids that can be rolled backward to (in other words, the checkpoints in the undo stack for this Store). They are in chronological order with the oldest checkpoint at the start of the array.
  • The current checkpoint Id of the Store's state, or undefined if the current state has not been checkpointed.
  • The 'forward' checkpoint Ids that can be rolled forward to (in other words, the checkpoints in the redo stack for this Store). They are in chronological order with the newest checkpoint at the end of the array.

Development type aliases

This is the collection of development type aliases within the checkpoints module. There is only one type alias, CheckpointsListenerStats.

CheckpointsListenerStats

The CheckpointsListenerStats type describes the number of listeners registered with the Checkpoints object, and can be used for debugging purposes.

{
  checkpointIds?: number;
  checkpoint?: number;
}
TypeDescription
checkpointIds?number

The number of CheckpointIdsListeners registered with the Checkpoints object.

checkpoint?number

The number of CheckpointListeners registered with the Checkpoints object.

A CheckpointsListenerStats object is returned from the getListenerStats method, and is only populated in a debug build.

persisters

The persisters module of the TinyBase project provides a simple framework for saving and loading Store data, to and from different destinations, or underlying storage types.

Several entry points are provided, each of which returns a new Persister object that can load and save a Store:

Since persistence requirements can be different for every app, the createCustomPersister function can also be used to easily create a fully customized way to save and load Store data.

See also

Persisting Data guide

Countries demo

Todo App demos

TinyDraw demo

Interfaces

There is one interface, Persister, within the persisters module.

Persister

A Persister object lets you save and load Store data to and from different locations, or underlying storage types.

This is useful for preserving Store data between browser sessions or reloads, saving or loading browser state to or from a server, or saving Store data to disk in a environment with filesystem access.

Creating a Persister depends on the choice of underlying storage where the data is to be stored. Options include the createSessionPersister function, the createLocalPersister, the createRemotePersister function, and the createFilePersister function. The createCustomPersister function can also be used to easily create a fully customized way to save and load Store data.

A Persister lets you explicit save or load data, with the save method and the load method respectively. These methods are both asynchronous (since the underlying data storage may also be) and return promises. As a result you should use the await keyword to call them in a way that guarantees subsequent execution order.

When you don't want to deal with explicit persistence operations, a Persister object also provides automatic saving and loading. Automatic saving listens for changes to the Store and persists the data immediately. Automatic loading listens (or polls) for changes to the persisted data and reflects those changes in the Store.

You can start automatic saving or loading with the startAutoSave method and startAutoLoad method. Both are asynchronous since they will do an immediate save and load before starting to listen for subsequent changes. You can stop the behavior with the stopAutoSave method and stopAutoLoad method (which are synchronous).

You may often want to have both automatic saving and loading of a Store so that changes are constantly synchronized (allowing basic state preservation between browser tabs, for example). The framework has some basic provisions to prevent race conditions - for example it will not attempt to save data if it is currently loading it and vice-versa.

Be aware, however, that the default implementations do not provide complex synchronization heuristics and you should comprehensively test your persistence strategy to understand the opportunity for data loss (in the case of trying to save data to a server under poor network conditions, for example).

Examples

This example creates a Store, persists it to the browser's session storage as a JSON string, changes the persisted data, updates the Store from it, and finally destroys the Persister again.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');

await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"fido":{"species":"dog"}}}'

sessionStorage.setItem('pets', '{"pets":{"toto":{"species":"dog"}}}');
await persister.load();
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}

persister.destroy();
sessionStorage.clear();

This example creates a Store, and automatically saves and loads it to the browser's session storage as a JSON string. Changes to the Store data, or the persisted data (implicitly firing a StorageEvent), are reflected accordingly.

const store = createStore();
const persister = createSessionPersister(store, 'pets');

await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();

store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"felix":{"species":"cat"}}}'

// In another browser tab:
sessionStorage.setItem('pets', '{"pets":{"toto":{"species":"dog"}}}');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})

// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}

persister.destroy();
sessionStorage.clear();
Getter methods

This is the collection of getter methods within the Persister interface. There is only one method, getStore.

getStore

The getStore method returns a reference to the underlying Store that is backing this Persister object.

getStore(): Store
returnsStore

A reference to the Store.

Example

This example creates a Persister object against a newly-created Store and then gets its reference in order to update its data.

const persister = createSessionPersister(createStore(), 'pets');
await persister.startAutoSave();

persister.getStore().setTables({pets: {fido: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"fido":{"species":"dog"}}}'

sessionStorage.clear();
Lifecycle methods

This is the collection of lifecycle methods within the Persister interface. There is only one method, destroy.

destroy

The destroy method should be called when this Persister object is no longer used.

destroy(): Persister
returnsPersister

This guarantees that all of the listeners that the object registered with the underlying Store and storage are removed and it can be correctly garbage collected. It is equivalent to running the stopAutoLoad method and the stopAutoSave method in succession.

Example

This example creates a Store, associates a Persister object with it (that registers a TablesListener with the underlying Store), and then destroys it again, removing the listener.

const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();

console.log(store.getListenerStats().tables);
// -> 1

persister.destroy();

console.log(store.getListenerStats().tables);
// -> 0
Load methods

This is the collection of load methods within the Persister interface. There are only three load methods, load, startAutoLoad, and stopAutoLoad.

load

The load method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once.

load(initialTables?: Tables): Promise<Persister>
TypeDescription
initialTables?Tables

An optional Tables object used when the underlying storage has not previously been populated.

returnsPromise<Persister>

A Promise containing a reference to the Persister object.

The optional parameter allows you to specify what the initial Tables object for the Store will be if there is nothing currently persisted. Using this instead of the initialTables parameter in the regular createStore function allows you to easily instantiate a Store whether it's loading from previously persisted storage or being run for the first time.

This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.

Examples

This example creates an empty Store, and loads data into it from the browser's session storage, which for the purposes of this example has been previously populated.

sessionStorage.setItem('pets', '{"pets":{"fido":{"species":"dog"}}}');

const store = createStore();
const persister = createSessionPersister(store, 'pets');

await persister.load();
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}

sessionStorage.clear();

This example creates an empty Store, and loads data into it from the browser's session storage, which is at first empty, so the optional parameter is used. The second time the load method is called, data has previously been persisted and instead, that is loaded.

const store = createStore();
const persister = createSessionPersister(store, 'pets');

await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}

sessionStorage.setItem('pets', '{"pets":{"toto":{"species":"dog"}}}');
await persister.load({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}

sessionStorage.clear();
startAutoLoad

The startAutoLoad method gets persisted data from storage, and loads it into the Store with which the Persister is associated, once, and then continuously.

startAutoLoad(initialTables?: Tables): Promise<Persister>
TypeDescription
initialTables?Tables

An optional Tables object used when the underlying storage has not previously been populated.

returnsPromise<Persister>

A Promise containing a reference to the Persister object.

The optional parameter allows you to specify what the initial Tables object for the Store will be if there is nothing at first persisted. Using this instead of the initialTables parameter in the regular createStore function allows you to easily instantiate a Store whether it's loading from previously persisted storage or being run for the first time.

This method first runs a single call to the load method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the underlying data (either through events or polling, depending on the storage type), automatically loading the data into the Store.

This method is asynchronous because it starts by making a single call to the asynchronous load method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.

Example

This example creates an empty Store, and loads data into it from the browser's session storage, which at first is empty (so the initialTables parameter is used). Subsequent changes to the underlying storage are then reflected in the Store (in this case through detection of StorageEvents from session storage changes made in another browser tab).

const store = createStore();
const persister = createSessionPersister(store, 'pets');

await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog'}}}

// In another browser tab:
sessionStorage.setItem('pets', '{"pets":{"toto":{"species":"dog"}}}');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})

// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}

persister.destroy();
sessionStorage.clear();
stopAutoLoad

The stopAutoLoad method stops the automatic loading of data from storage previously started with the startAutoLoad method.

stopAutoLoad(): Persister
returnsPersister

A reference to the Persister object.

If the Persister is not currently set to automatically load, this method has no effect.

Example

This example creates an empty Store, and starts automatically loading data into it from the browser's session storage. Once the automatic loading is stopped, subsequent changes are not reflected in the Store.

const store = createStore();
const persister = createSessionPersister(store, 'pets');
await persister.startAutoLoad();

// In another browser tab:
sessionStorage.setItem('pets', '{"pets":{"toto":{"species":"dog"}}}');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}

persister.stopAutoLoad();

// In another browser tab:
sessionStorage.setItem('pets', '{"pets":{"felix":{"species":"cat"}}}');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...
console.log(store.getTables());
// -> {pets: {toto: {species: 'dog'}}}
// Storage change has not been automatically loaded.

persister.destroy();
sessionStorage.clear();
Save methods

This is the collection of save methods within the Persister interface. There are only three save methods, save, startAutoSave, and stopAutoSave.

save

The save method takes data from the Store with which the Persister is associated and persists it into storage, once.

save(): Promise<Persister>
returnsPromise<Persister>

A Promise containing a reference to the Persister object.

This method is asynchronous because the persisted data may be on a remote machine or a filesystem. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.

Example

This example creates a Store with some data, and saves into the browser's session storage.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');

await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"fido":{"species":"dog"}}}'

persister.destroy();
sessionStorage.clear();
startAutoSave

The save method takes data from the Store with which the Persister is associated and persists it into storage, once, and then continuously.

startAutoSave(): Promise<Persister>
returnsPromise<Persister>

A Promise containing a reference to the Persister object.

This method first runs a single call to the save method to ensure the data is in sync with the persisted storage. It then continues to watch for changes to the Store, automatically saving the data to storage.

This method is asynchronous because it starts by making a single call to the asynchronous save method. Even for those storage types that are synchronous (like browser storage) it is still recommended that you await calls to this method or handle the return type natively as a Promise.

Example

This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');

await persister.startAutoSave();
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"fido":{"species":"dog"}}}'

store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"toto":{"species":"dog"}}}'

sessionStorage.clear();
stopAutoSave

The stopAutoSave method stops the automatic save of data to storage previously started with the startAutoSave method.

stopAutoSave(): Persister
returnsPersister

A reference to the Persister object.

If the Persister is not currently set to automatically save, this method has no effect.

Example

This example creates a Store with some data, and saves into the browser's session storage. Subsequent changes to the Store are then automatically saved to the underlying storage. Once the automatic saving is stopped, subsequent changes are not reflected.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');
await persister.startAutoSave();

store.setTables({pets: {toto: {species: 'dog'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"toto":{"species":"dog"}}}'

persister.stopAutoSave();

store.setTables({pets: {felix: {species: 'cat'}}});
// ...
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"toto":{"species":"dog"}}}'
// Store change has not been automatically saved.

sessionStorage.clear();
Development methods

This is the collection of development methods within the Persister interface. There is only one method, getStats.

getStats

The getStats method provides a set of statistics about the Persister, and is used for debugging purposes.

getStats(): PersisterStats
returnsPersisterStats

A PersisterStats object containing Persister load and save statistics.

The PersisterStats object contains a count of the number of times the Persister has loaded and saved data.

The statistics are only populated in a debug build: production builds return an empty object. The method is intended to be used during development to ensure your persistence layer is acting as expected, for example.

Example

This example gets the load and save statistics of a Persister object. Remember that the startAutoLoad method invokes an explicit load when it starts, and the startAutoSave method invokes an explicit save when it starts - so those numbers are included in addition to the loads and saves invoked by changes to the Store and to the underlying storage.

const store = createStore();
const persister = createSessionPersister(store, 'pets');

await persister.startAutoLoad({pets: {fido: {species: 'dog'}}});
await persister.startAutoSave();

store.setTables({pets: {felix: {species: 'cat'}}});
// ...

sessionStorage.setItem('pets', '{"pets":{"toto":{"species":"dog"}}}');
// -> StorageEvent('storage', {storageArea: sessionStorage, key: 'pets'})
// ...

console.log(persister.getStats());
// -> {loads: 2, saves: 2}

persister.destroy();
sessionStorage.clear();

Functions

These are the functions within the persisters module.

createCustomPersister

The createCustomPersister function creates an Persister object that you can configure to persist the Store in any way you wish.

createCustomPersister(
  store: Store,
  getPersisted: () => Promise<undefined | null | string>,
  setPersisted: (json: string) => Promise<void>,
  startListeningToPersisted: (didChange: Callback) => void,
  stopListeningToPersisted: Callback,
): Persister
TypeDescription
storeStore

The Store to persist.

getPersisted() => Promise<undefined | null | string>

An asynchronous function which will fetch JSON from the persistence layer (or null or undefined if not present).

setPersisted(json: string) => Promise<void>

An asynchronous function which will send JSON to the persistence layer.

startListeningToPersisted(didChange: Callback) => void

A function that will register a didChange listener on underlying changes to the persistence layer.

stopListeningToPersistedCallback

A function that will unregister the listener from the underlying changes to the persistence layer.

returnsPersister

A reference to the new Persister object.

As well as providing a reference to the Store to persist, you must provide functions that handle how to fetch, write, and listen to, the persistence layer.

The other creation functions (such as the createSessionPersister function and createFilePersister function, for example) all use this function under the covers. See those implementations for ideas on how to implement your own Persister types.

Example

This example creates a custom Persister object and persists the Store to a local string called storeJson and which would automatically load by polling for changes every second.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
let storeJson;
let interval;

const persister = createCustomPersister(
  store,
  async () => storeJson,
  async (json) => (storeJson = json),
  (didChange) => (interval = setInterval(didChange, 1000)),
  () => clearInterval(interval),
);

await persister.save();
console.log(storeJson);
// -> '{"pets":{"fido":{"species":"dog"}}}'

storeJson = '{"pets":{"fido":{"species":"dog","color":"brown"}}}';
await persister.load();

console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}

persister.destroy();

createFilePersister

The createFilePersister function creates an Persister object that can persist the Store to a local file (in an appropriate environment).

createFilePersister(
  store: Store,
  filePath: string,
): Persister
TypeDescription
storeStore

The Store to persist.

filePathstring

The location of the local file to persist the Store to.

returnsPersister

A reference to the new Persister object.

As well as providing a reference to the Store to persist, you must provide filePath parameter which identifies the file to persist it to.

Example

This example creates a Persister object and persists the Store to a local file.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createFilePersister(store, '/app/persisted.json');

await persister.save();
// Store JSON will be saved to the file.

await persister.load();
// Store JSON will be loaded from the file.

persister.destroy();

createLocalPersister

The createLocalPersister function creates an Persister object that can persist the Store to the browser's local storage.

createLocalPersister(
  store: Store,
  storageName: string,
): Persister
TypeDescription
storeStore

The Store to persist.

storageNamestring

The unique key to identify the storage location.

returnsPersister

A reference to the new Persister object.

As well as providing a reference to the Store to persist, you must provide a storageName parameter which is unique to your application. This is the key that the browser uses to identify the storage location.

Example

This example creates a Persister object and persists the Store to the browser's local storage.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createLocalPersister(store, 'pets');

await persister.save();
console.log(localStorage.getItem('pets'));
// -> '{"pets":{"fido":{"species":"dog"}}}'

persister.destroy();
localStorage.clear();

createRemotePersister

The createRemotePersister function creates an Persister object that can persist the Store to a remote server.

createRemotePersister(
  store: Store,
  loadUrl: string,
  saveUrl: string,
  autoLoadIntervalSeconds: number,
): Persister
TypeDescription
storeStore

The Store to persist.

loadUrlstring

The endpoint that supports a GET method to load JSON.

saveUrlstring

The endpoint that supports a POST method to save JSON.

autoLoadIntervalSecondsnumber

How often to poll the loadUrl when automatically loading changes from the server.

returnsPersister

A reference to the new Persister object.

As well as providing a reference to the Store to persist, you must provide loadUrl and saveUrl parameters. These identify the endpoints of the server that support the GET method (to fetch the Store JSON to load) and the POST method (to send the Store JSON to save) respectively.

For when you choose to enable automatic loading for the Persister (with the startAutoLoad method), it will poll the loadUrl for changes. The autoLoadIntervalSeconds method is used to indicate how often to do this.

Example

This example creates a Persister object and persists the Store to a remote server.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createRemotePersister(
  store,
  'https://example.com/load',
  'https://example.com/save',
  5,
);

await persister.save();
// Store JSON will be sent to server in a POST request.

await persister.load();
// Store JSON will be fetched from server with a GET request.

persister.destroy();

createSessionPersister

The createSessionPersister function creates an Persister object that can persist the Store to the browser's session storage.

createSessionPersister(
  store: Store,
  storageName: string,
): Persister
TypeDescription
storeStore

The Store to persist.

storageNamestring

The unique key to identify the storage location.

returnsPersister

A reference to the new Persister object.

As well as providing a reference to the Store to persist, you must provide a storageName parameter which is unique to your application. This is the key that the browser uses to identify the storage location.

Example

This example creates a Persister object and persists the Store to the browser's session storage.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const persister = createSessionPersister(store, 'pets');

await persister.save();
console.log(sessionStorage.getItem('pets'));
// -> '{"pets":{"fido":{"species":"dog"}}}'

persister.destroy();
sessionStorage.clear();

Type aliases

There is one type alias, PersisterStats, within the persisters module.

PersisterStats

The PersisterStats type describes the number of times a Persister object has loaded or saved data.

{
  loads?: number;
  saves?: number;
}
TypeDescription
loads?number
saves?number

A PersisterStats object is returned from the getStats method, and is only populated in a debug build.

ui-react

The ui-react module of the TinyBase project provides both hooks and components to make it easy to create reactive apps with Store objects.

The hooks in this module provide access to the data and structures exposed by other modules in the project. As well as immediate access, they all register listeners such that components using those hooks are selectively re-rendered when data changes.

The components in this module provide a further abstraction over those hooks to ease the composition of user interfaces that use TinyBase.

See also

Building UIs guides

Building UIs With Metrics guide

Building UIs With Indexes guide

Building UIs With Relationships guide

Building UIs With Checkpoints guide

Countries demo

Todo App demos

TinyDraw demo

Functions

These are the functions within the ui-react module.

Checkpoints hooks

This is the collection of checkpoints hooks within the ui-react module. There are 12 checkpoints hooks in total.

useCheckpoint

The useCheckpoint hook returns the label for a checkpoint, and registers a listener so that any changes to that result will cause a re-render.

useCheckpoint(
  checkpointId: string,
  checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): string | undefined
TypeDescription
checkpointIdstring

The Id of the checkpoint.

checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to be accessed: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

returnsstring | undefined

A string label for the requested checkpoint, an empty string if it was never set, or `undefined` if the checkpoint does not exist.

A Provider component is used to wrap part of an application in a context, and it can contain a default Checkpoints object or a set of Checkpoints objects named by Id. The useCheckpoint hook lets you indicate which Checkpoints object to get data for: omit the optional final parameter for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide a Checkpoints object explicitly by reference.

When first rendered, this hook will create a listener so that changes to the label will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Checkpoints object outside the application, which is used in the useCheckpoint hook by reference. A change to the checkpoint label re-renders the component.

const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => <span>{useCheckpoint('1', checkpoints)}</span>;

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span></span>'

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<span>sale</span>'

This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpoint hook.

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useCheckpoint('0')}</span>;

const checkpoints = createCheckpoints(
  createStore().setTable('pets', {fido: {species: 'dog'}}),
).setCheckpoint('0', 'initial');

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<span>initial</span>'

This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpoint hook.

const App = ({checkpoints}) => (
  <Provider checkpointsById={{petCheckpoints: checkpoints}}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useCheckpoint('0', 'petCheckpoints')}</span>;

const checkpoints = createCheckpoints(
  createStore().setTable('pets', {fido: {species: 'dog'}}),
).setCheckpoint('0', 'initial');

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<span>initial</span>'
useCheckpointIds

The useCheckpointIds hook returns an array of the checkpoint Ids being managed by this Checkpoints object, and registers a listener so that any changes to that result will cause a re-render.

useCheckpointIds(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): CheckpointIds
TypeDescription
checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to be accessed: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

returnsCheckpointIds

A CheckpointIds array, containing the checkpoint Ids managed by this Checkpoints object.

A Provider component is used to wrap part of an application in a context, and it can contain a default Checkpoints object or a set of Checkpoints objects named by Id. The useCheckpointIds hook lets you indicate which Checkpoints object to get data for: omit the optional parameter for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide a Checkpoints object explicitly by reference.

When first rendered, this hook will create a listener so that changes to the checkpoint Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Checkpoints object outside the application, which is used in the useCheckpointIds hook by reference. A change to the checkpoint Ids re-renders the component.

const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => (
  <span>{JSON.stringify(useCheckpointIds(checkpoints))}</span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'

store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> '<span>[["0"],null,[]]</span>'

checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<span>[["0"],"1",[]]</span>'

This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpointIds hook.

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useCheckpointIds())}</span>;

const checkpoints = createCheckpoints(
  createStore().setTable('pets', {fido: {species: 'dog'}}),
);

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'

This example creates a Provider context into which a default Checkpoints object is provided. A component within it then uses the useCheckpointIds hook.

const App = ({checkpoints}) => (
  <Provider checkpointsById={{petCheckpoints: checkpoints}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useCheckpointIds('petCheckpoints'))}</span>
);

const checkpoints = createCheckpoints(
  createStore().setTable('pets', {fido: {species: 'dog'}}),
);

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
useCheckpointIdsListener

The useCheckpointIdsListener hook registers a listener function with the Checkpoints object that will be called whenever its set of checkpoints changes.

useCheckpointIdsListener(
  listener: CheckpointIdsListener,
  listenerDeps?: DependencyList,
  checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): void
TypeDescription
listenerCheckpointIdsListener

The function that will be called whenever the checkpoints change.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to register the listener with: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCheckpointIds hook).

Unlike the addCheckpointIdsListener method, which returns a listener Id and requires you to remove it manually, the useCheckpointIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Checkpoints object will be deleted.

Example

This example uses the useCheckpointIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useCheckpointIdsListener(() => console.log('Checkpoint Ids changed'));
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(checkpoints.getListenerStats().checkpointIds);
// -> 1

store.setCell('pets', 'fido', 'sold', true);
// -> 'Checkpoint Ids changed'
checkpoints.addCheckpoint();
// -> 'Checkpoint Ids changed'

ReactDOM.unmountComponentAtNode(app);
console.log(checkpoints.getListenerStats().checkpointIds);
// -> 0
useCheckpointListener

The useCheckpointListener hook registers a listener function with the Checkpoints object that will be called whenever the label of a checkpoint changes.

useCheckpointListener(
  checkpointId: IdOrNull,
  listener: CheckpointListener,
  listenerDeps?: DependencyList,
  checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
): void
TypeDescription
checkpointIdIdOrNull

The Id of the checkpoint to listen to, or null as a wildcard.

listenerCheckpointListener

The function that will be called whenever the checkpoint label changes.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to register the listener with: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCheckpoint hook).

You can either listen to a single checkpoint label (by specifying the checkpoint Id as the method's first parameter), or changes to any checkpoint label (by providing a null wildcard).

Unlike the addCheckpointListener method, which returns a listener Id and requires you to remove it manually, the useCheckpointListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Checkpoints object will be deleted.

Example

This example uses the useCheckpointListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useCheckpointListener('0', () =>
    console.log('Checkpoint label changed'),
  );
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {sold: false}}});
const checkpoints = createCheckpoints(store);

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(checkpoints.getListenerStats().checkpoint);
// -> 1

checkpoints.setCheckpoint('0', 'initial');
// -> 'Checkpoint label changed'

ReactDOM.unmountComponentAtNode(app);
console.log(checkpoints.getListenerStats().checkpoint);
// -> 0
useCheckpoints

The useCheckpoints hook is used to get a reference to a Checkpoints object from within a Provider component context.

useCheckpoints(id?: string): Checkpoints | undefined
TypeDescription
id?string

An optional Id for accessing a Checkpoints object that was named with an Id in the Provider.

returnsCheckpoints | undefined

A reference to the Checkpoints object (or `undefined` if not within a Provider context, or if the requested Checkpoints object does not exist)

A Provider component is used to wrap part of an application in a context. It can contain a default Checkpoints object (or a set of Checkpoints objects named by Id) that can be easily accessed without having to be passed down as props through every component.

The useCheckpoints hook lets you either get a reference to the default Checkpoints object (when called without an parameter), or one of the Checkpoints objects that are named by Id (when called with an Id parameter).

Examples

This example creates a Provider context into which a default Checkpoint object is provided. A component within it then uses the useCheckpoints hook to get a reference to the Checkpoints object again, without the need to have it passed as a prop.

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{useCheckpoints().getListenerStats().checkpointIds}</span>
);

const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'

This example creates a Provider context into which a Checkpoints object is provided, named by Id. A component within it then uses the useCheckpoints hook with that Id to get a reference to the Checkpoints object again, without the need to have it passed as a prop.

const App = ({checkpoints}) => (
  <Provider checkpointsById={{petStore: checkpoints}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>
    {useCheckpoints('petStore').getListenerStats().checkpointIds}
  </span>
);

const checkpoints = createCheckpoints(createStore());
const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'
useCreateCheckpoints

The useCreateCheckpoints hook is used to create a Checkpoints object within a React application with convenient memoization.

useCreateCheckpoints(
  store: Store,
  create: (store: Store) => Checkpoints,
  createDeps?: DependencyList,
): Checkpoints
TypeDescription
storeStore

A reference to the Store for which to create a new Checkpoints object.

create(store: Store) => Checkpoints

A function for performing the creation steps of the Checkpoints object for the Store, plus any additional steps such as adding definitions or listeners, and returning it.

createDeps?DependencyList

An optional array of dependencies for the create function, which, if any change, result in its rerun. This parameter defaults to an empty array.

returnsCheckpoints

A reference to the Checkpoints object.

It is possible to create a Checkpoints object outside of the React app with the regular createCheckpoints function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Checkpoints object being created every time the app renders or re-renders, the useCreateCheckpoints hook wraps the creation in a memoization.

The useCreateCheckpoints hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.

If your create function contains other dependencies, the changing of which should also cause the Checkpoints object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.

This hook ensures the Checkpoints object is destroyed whenever a new one is created or the component is unmounted.

Examples

This example creates a Checkpoints object at the top level of a React application. Even though the App component is rendered twice, the Checkpoints object creation only occurs once by default.

const App = () => {
  const store = useCreateStore(createStore);
  const checkpoints = useCreateCheckpoints(store, (store) => {
    console.log('Checkpoints created');
    return createCheckpoints(store).setSize(10);
  });
  return <span>{JSON.stringify(checkpoints.getCheckpointIds())}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
// -> 'Checkpoints created'

ReactDOM.render(<App />, app);
// No second Checkpoints creation

console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'

This example creates a Checkpoints object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateCheckpoints hook takes the size prop as a dependency, and so the Checkpoints object is created again on the second render.

const App = ({size}) => {
  const store = useCreateStore(createStore);
  const checkpoints = useCreateCheckpoints(
    store,
    (store) => {
      console.log(`Checkpoints created, size ${size}`);
      return createCheckpoints(store).setSize(size);
    },
    [size],
  );
  return <span>{JSON.stringify(checkpoints.getCheckpointIds())}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App size={20} />, app);
// -> 'Checkpoints created, size 20'

console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'

ReactDOM.render(<App size={50} />, app);
// -> 'Checkpoints created, size 50'

console.log(app.innerHTML);
// -> '<span>[[],"0",[]]</span>'
useGoBackwardCallback

The useGoBackwardCallback hook returns a callback that moves the state of the underlying Store back to the previous checkpoint, effectively performing an 'undo' on the Store data.

useGoBackwardCallback(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Callback
TypeDescription
checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to use to go backward: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

returnsCallback

A callback for subsequent use.

This hook is useful, for example, when creating an event handler that will go backward to the previous checkpoint - such as when clicking an undo button.

If there is no previous checkpoint to return to, this callback has no effect.

Example

This example uses the useGoBackwardCallback hook to create an event handler which goes backward in the checkpoint stack when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => (
  <span id="span" onClick={useGoBackwardCallback(checkpoints)}>
    Backward
  </span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');

store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
useGoForwardCallback

The useGoForwardCallback hook returns a callback that moves the state of the underlying Store forwards to a future checkpoint, effectively performing an 'redo' on the Store data.

useGoForwardCallback(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): Callback
TypeDescription
checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to use to go backward: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

returnsCallback

A callback for subsequent use.

This hook is useful, for example, when creating an event handler that will go forward to the next checkpoint - such as when clicking an redo button.

If there is no future checkpoint to return to, this callback has no effect.

Example

This example uses the useGoForwardCallback hook to create an event handler which goes backward in the checkpoint stack when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => (
  <span id="span" onClick={useGoForwardCallback(checkpoints)}>
    Forward
  </span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');

store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]

checkpoints.goBackward();
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]
useGoToCallback

The useGoToCallback hook returns a parameterized callback that can be used to move the state of the underlying Store backwards or forwards to a specified checkpoint.

useGoToCallback<Parameter>(
  getCheckpointId: (parameter: Parameter) => string,
  getCheckpointIdDeps?: DependencyList,
  checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
  then?: (checkpoints: Checkpoints, checkpointId: string) => void,
  thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
TypeDescription
getCheckpointId(parameter: Parameter) => string

A function which returns an Id that will be used to indicate which checkpoint to move to, based on the parameter the callback will receive (and which is most likely a DOM event).

getCheckpointIdDeps?DependencyList

An optional array of dependencies for the getCheckpointId function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to be updated: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

then?(checkpoints: Checkpoints, checkpointId: string) => void

A function which is called after the checkpoint is moved, with a reference to the Checkpoints object and the checkpoint Id moved to.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback.

returnsParameterizedCallback<Parameter>

A parameterized callback for subsequent use. This parameter defaults to an empty array.

This hook is useful, for example, when creating an event handler that will move the checkpoint. In this case, the parameter will likely be the event, so that you can use data from it as the checkpoint Id to move to.

The optional first parameter is a function which will produce the label that will then be used to name the checkpoint.

If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the checkpoint has been set.

The Checkpoints object for which the callback will set the checkpoint (indicated by the hook's checkpointsOrCheckpointsId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useGoToCallback hook to create an event handler which moves to a checkpoint when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
  const handleClick = useGoToCallback(() => '0', [], checkpoints);
  return (
    <span id="span" onClick={handleClick}>
      Goto 0
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');

store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint();
console.log(checkpoints.getCheckpointIds());
// -> [["0"], "1", []]

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
console.log(checkpoints.getCheckpointIds());
// -> [[], "0", ["1"]]
useRedoInformation

The useRedoInformation hook returns an UndoOrRedoInformation array that indicates if and how you can move the state of the underlying Store forwards to a future checkpoint.

useRedoInformation(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): UndoOrRedoInformation
TypeDescription
checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to use to go backward: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

returnsUndoOrRedoInformation

UndoOrRedoInformation about if and how you can move the state of the underlying Store forward.

This hook is useful if you are building an redo button: the information contains whether a redo action is available (to enable the button), the callback to perform the redo action, the checkpoint Id that will be redone, and its label, if available.

Example

This example uses the useUndoInformation hook to create a redo button.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
  const [canRedo, handleRedo, id, label] = useRedoInformation(checkpoints);
  return canRedo ? (
    <span onClick={handleRedo}>Redo {label}</span>
  ) : (
    <span>Nothing to redo</span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>Nothing to redo</span>'

store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint('color');
checkpoints.goTo('0');
console.log(app.innerHTML);
// -> '<span>Redo color</span>'
useSetCheckpointCallback

The useSetCheckpointCallback hook returns a parameterized callback that can be used to record a checkpoint of a Store into a Checkpoints object that can be reverted to in the future.

useSetCheckpointCallback<Parameter>(
  getCheckpoint?: (parameter: Parameter) => string,
  getCheckpointDeps?: DependencyList,
  checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId,
  then?: (checkpointId: string, checkpoints: Checkpoints, label?: string) => void,
  thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
TypeDescription
getCheckpoint?(parameter: Parameter) => string

An optional function which returns a string that will be used to describe the actions leading up to this checkpoint, based on the parameter the callback will receive (and which is most likely a DOM event).

getCheckpointDeps?DependencyList

An optional array of dependencies for the getCheckpoint function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to be updated: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

then?(checkpointId: string, checkpoints: Checkpoints, label?: string) => void

A function which is called after the checkpoint is set, with the new checkpoint Id, a reference to the Checkpoints object and the label provided, if any.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsParameterizedCallback<Parameter>

A parameterized callback for subsequent use.

This hook is useful, for example, when creating an event handler that will set the checkpoint. In this case, the parameter will likely be the event, so that you can use data from it as the checkpoint label.

The optional first parameter is a function which will produce the label that will then be used to name the checkpoint.

If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the checkpoint has been set.

The Checkpoints object for which the callback will set the checkpoint (indicated by the hook's checkpointsOrCheckpointsId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useSetCheckpointCallback hook to create an event handler which sets a checkpoint when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
  const handleClick = useSetCheckpointCallback(
    (e) => `with #${e.target.id} button`,
    [],
    checkpoints,
    (checkpointId, checkpoints, label) =>
      console.log(`Checkpoint ${checkpointId} set, ${label}`),
  );
  return (
    <span id="span" onClick={handleClick}>
      Set
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');

store.setCell('pets', 'nemo', 'color', 'orange');

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Checkpoint 1 set, with #span button'
useUndoInformation

The useUndoInformation hook returns an UndoOrRedoInformation array that indicates if and how you can move the state of the underlying Store backward to the previous checkpoint.

useUndoInformation(checkpointsOrCheckpointsId?: CheckpointsOrCheckpointsId): UndoOrRedoInformation
TypeDescription
checkpointsOrCheckpointsId?CheckpointsOrCheckpointsId

The Checkpoints object to use to go backward: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

returnsUndoOrRedoInformation

UndoOrRedoInformation about if and how you can move the state of the underlying Store backward.

This hook is useful if you are building an undo button: the information contains whether an undo action is available (to enable the button), the callback to perform the undo action, the current checkpoint Id that will be undone, and its label, if available.

Example

This example uses the useUndoInformation hook to create an undo button.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const checkpoints = createCheckpoints(store);
const App = () => {
  const [canUndo, handleUndo, id, label] = useUndoInformation(checkpoints);
  return canUndo ? (
    <span onClick={handleUndo}>Undo {label}</span>
  ) : (
    <span>Nothing to undo</span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>Nothing to undo</span>'

store.setCell('pets', 'nemo', 'color', 'orange');
checkpoints.addCheckpoint('color');
console.log(app.innerHTML);
// -> '<span>Undo color</span>'

Indexes hooks

This is the collection of indexes hooks within the ui-react module. There are 6 indexes hooks in total.

useSliceRowIds

The useSliceRowIds hook gets the list of Row Ids in a given Slice, and registers a listener so that any changes to that result will cause a re-render.

useSliceRowIds(
  indexId: string,
  sliceId: string,
  indexesOrIndexesId?: IndexesOrIndexesId,
): Ids
TypeDescription
indexIdstring

The Id of the Index.

sliceIdstring

The Id of the Slice in the Index.

indexesOrIndexesId?IndexesOrIndexesId

The Indexes object to be accessed: omit for the default context Indexes object, provide an Id for a named context Indexes object, or provide an explicit reference.

returnsIds

The Row Ids in the Slice, or an empty array.

A Provider component is used to wrap part of an application in a context, and it can contain a default Indexes object or a set of Indexes objects named by Id. The useSliceRowIds hook lets you indicate which Indexes object to get data for: omit the optional final parameter for the default context Indexes object, provide an Id for a named context Indexes object, or provide an Indexes object explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Row Ids in the Slice will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates an Indexes object outside the application, which is used in the useSliceRowIds hook by reference. A change to the Row Ids in the Slice re-renders the component.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
  <span>
    {JSON.stringify(useSliceRowIds('bySpecies', 'dog', indexes))}
  </span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'

store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<span>["fido","cujo","toto"]</span>'

This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceRowIds hook.

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useSliceRowIds('bySpecies', 'dog'))}</span>
);

const indexes = createIndexes(
  createStore().setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  }),
).setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'

This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceRowIds hook.

const App = ({indexes}) => (
  <Provider indexesById={{petIndexes: indexes}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>
    {JSON.stringify(useSliceRowIds('bySpecies', 'dog', 'petIndexes'))}
  </span>
);

const indexes = createIndexes(
  createStore().setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  }),
).setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
useSliceRowIdsListener

The useSliceRowIdsListener hook registers a listener function with the Indexes object that will be called whenever the Row Ids in a Slice change.

useSliceRowIdsListener(
  indexId: IdOrNull,
  sliceId: IdOrNull,
  listener: SliceRowIdsListener,
  listenerDeps?: DependencyList,
  indexesOrIndexesId?: IndexesOrIndexesId,
): void
TypeDescription
indexIdIdOrNull

The Id of the Index to listen to, or null as a wildcard.

sliceIdIdOrNull

The Id of the Slice to listen to, or null as a wildcard.

listenerSliceRowIdsListener

The function that will be called whenever the Row Ids in the Slice change.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

indexesOrIndexesId?IndexesOrIndexesId

The Indexes object to register the listener with: omit for the default context Indexes object, provide an Id for a named context Indexes object, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSliceRowIds hook).

You can either listen to a single Slice (by specifying the Index Id and Slice Id as the method's first two parameters), or changes to any Slice (by providing null wildcards).

Both, either, or neither of the indexId and sliceId parameters can be wildcarded with null. You can listen to a specific Slice in a specific Index, any Slice in a specific Index, a specific Slice in any Index, or any Slice in any Index.

Unlike the addSliceRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useSliceRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.

Example

This example uses the useSliceRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Indexes object.

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useSliceRowIdsListener('bySpecies', 'dog', () =>
    console.log('Slice Row Ids changed'),
  );
  return <span>App</span>;
};

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(indexes.getListenerStats().sliceRowIds);
// -> 1

store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Slice Row Ids changed'

ReactDOM.unmountComponentAtNode(app);
console.log(indexes.getListenerStats().sliceRowIds);
// -> 0
useCreateIndexes

The useCreateIndexes hook is used to create an Indexes object within a React application with convenient memoization.

useCreateIndexes(
  store: Store,
  create: (store: Store) => Indexes,
  createDeps?: DependencyList,
): Indexes
TypeDescription
storeStore

A reference to the Store for which to create a new Indexes object.

create(store: Store) => Indexes

A function for performing the creation steps of the Indexes object for the Store, plus any additional steps such as adding definitions or listeners, and returning it.

createDeps?DependencyList

An optional array of dependencies for the create function, which, if any change, result in its rerun. This parameter defaults to an empty array.

returnsIndexes

A reference to the Indexes object.

It is possible to create an Indexes object outside of the React app with the regular createIndexes function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Indexes object being created every time the app renders or re-renders, the useCreateIndexes hook wraps the creation in a memoization.

The useCreateIndexes hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.

If your create function contains other dependencies, the changing of which should also cause the Indexes object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.

This hook ensures the Indexes object is destroyed whenever a new one is created or the component is unmounted.

Examples

This example creates an Indexes object at the top level of a React application. Even though the App component is rendered twice, the Indexes object creation only occurs once by default.

const App = () => {
  const store = useCreateStore((store) =>
    createStore().setTable('pets', {
      fido: {species: 'dog'},
      felix: {species: 'cat'},
      cujo: {species: 'dog'},
    }),
  );
  const indexes = useCreateIndexes(store, (store) => {
    console.log('Indexes created');
    return createIndexes(store).setIndexDefinition(
      'bySpecies',
      'pets',
      'species',
    );
  });
  return <span>{JSON.stringify(indexes.getSliceIds('bySpecies'))}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
// -> 'Indexes created'

ReactDOM.render(<App />, app);
// No second Indexes creation

console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'

This example creates an Indexes object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateIndexes hook takes the cellToIndex prop as a dependency, and so the Indexes object is created again on the second render.

const App = ({cellToIndex}) => {
  const store = useCreateStore(() =>
    createStore().setTable('pets', {
      fido: {species: 'dog', color: 'brown'},
      felix: {species: 'cat', color: 'black'},
      cujo: {species: 'dog', color: 'brown'},
    }),
  );
  const indexes = useCreateIndexes(
    store,
    (store) => {
      console.log(`Index created for ${cellToIndex} cell`);
      return createIndexes(store).setIndexDefinition(
        'byCell',
        'pets',
        cellToIndex,
      );
    },
    [cellToIndex],
  );
  return <span>{JSON.stringify(indexes.getSliceIds('byCell'))}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App cellToIndex="species" />, app);
// -> 'Index created for species cell'

console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'

ReactDOM.render(<App cellToIndex="color" />, app);
// -> 'Index created for color cell'

console.log(app.innerHTML);
// -> '<span>["brown","black"]</span>'
useIndexes

The useIndexes hook is used to get a reference to an Indexes object from within a Provider component context.

useIndexes(id?: string): Indexes | undefined
TypeDescription
id?string

An optional Id for accessing an Indexes object that was named with an Id in the Provider.

returnsIndexes | undefined

A reference to the Indexes object (or `undefined` if not within a Provider context, or if the requested Indexes object does not exist)

A Provider component is used to wrap part of an application in a context. It can contain a default Indexes object (or a set of Indexes objects named by Id) that can be easily accessed without having to be passed down as props through every component.

The useIndexes hook lets you either get a reference to the default Indexes object (when called without an parameter), or one of the Indexes objects that are named by Id (when called with an Id parameter).

Examples

This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useIndexes hook to get a reference to the Indexes object again, without the need to have it passed as a prop.

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useIndexes().getListenerStats().sliceIds}</span>;

const indexes = createIndexes(createStore());
const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'

This example creates a Provider context into which an Indexes object is provided, named by Id. A component within it then uses the useIndexes hook with that Id to get a reference to the Indexes object again, without the need to have it passed as a prop.

const App = ({indexes}) => (
  <Provider indexesById={{petStore: indexes}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{useIndexes('petStore').getListenerStats().sliceIds}</span>
);

const indexes = createIndexes(createStore());
const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'
useSliceIds

The useSliceIds hook gets the list of Slice Ids in an Index, and registers a listener so that any changes to that result will cause a re-render.

useSliceIds(
  indexId: string,
  indexesOrIndexesId?: IndexesOrIndexesId,
): Ids
TypeDescription
indexIdstring

The Id of the Index.

indexesOrIndexesId?IndexesOrIndexesId

The Indexes object to be accessed: omit for the default context Indexes object, provide an Id for a named context Indexes object, or provide an explicit reference.

returnsIds

The Slice Ids in the Index, or an empty array.

A Provider component is used to wrap part of an application in a context, and it can contain a default Indexes object or a set of Indexes objects named by Id. The useSliceIds hook lets you indicate which Indexes object to get data for: omit the optional final parameter for the default context Indexes object, provide an Id for a named context Indexes object, or provide a Indexes object explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Slice Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates an Indexes object outside the application, which is used in the useSliceIds hook by reference. A change to the Slice Ids re-renders the component.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
  <span>{JSON.stringify(useSliceIds('bySpecies', indexes))}</span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'

store.setRow('pets', 'lowly', {species: 'worm'});
console.log(app.innerHTML);
// -> '<span>["dog","cat","worm"]</span>'

This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceIds hook.

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useSliceIds('bySpecies'))}</span>;

const indexes = createIndexes(
  createStore().setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  }),
).setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'

This example creates a Provider context into which a default Indexes object is provided. A component within it then uses the useSliceIds hook.

const App = ({indexes}) => (
  <Provider indexesById={{petIndexes: indexes}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useSliceIds('bySpecies', 'petIndexes'))}</span>
);

const indexes = createIndexes(
  createStore().setTable('pets', {
    fido: {species: 'dog'},
    felix: {species: 'cat'},
    cujo: {species: 'dog'},
  }),
).setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<span>["dog","cat"]</span>'
useSliceIdsListener

The useSliceIdsListener hook registers a listener function with the Indexes object that will be called whenever the Slice Ids in an Index change.

useSliceIdsListener(
  indexId: IdOrNull,
  listener: SliceIdsListener,
  listenerDeps?: DependencyList,
  indexesOrIndexesId?: IndexesOrIndexesId,
): void
TypeDescription
indexIdIdOrNull

The Id of the Index to listen to, or null as a wildcard.

listenerSliceIdsListener

The function that will be called whenever the Slice Ids in the Index change.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

indexesOrIndexesId?IndexesOrIndexesId

The Indexes object to register the listener with: omit for the default context Indexes object, provide an Id for a named context Indexes object, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useSliceIds hook).

You can either listen to a single Index (by specifying the Index Id as the method's first parameter), or changes to any Index (by providing a null wildcard).

Unlike the addSliceIdsListener method, which returns a listener Id and requires you to remove it manually, the useSliceIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.

Example

This example uses the useSliceIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Indexes object.

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useSliceIdsListener('bySpecies', () => console.log('Slice Ids changed'));
  return <span>App</span>;
};

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(indexes.getListenerStats().sliceIds);
// -> 1

store.setRow('pets', 'lowly', {species: 'worm'});
// -> 'Slice Ids changed'

ReactDOM.unmountComponentAtNode(app);
console.log(indexes.getListenerStats().sliceIds);
// -> 0

Metrics hooks

This is the collection of metrics hooks within the ui-react module. There are 4 metrics hooks in total.

useCreateMetrics

The useCreateMetrics hook is used to create a Metrics object within a React application with convenient memoization.

useCreateMetrics(
  store: Store,
  create: (store: Store) => Metrics,
  createDeps?: DependencyList,
): Metrics
TypeDescription
storeStore

A reference to the Store for which to create a new Metrics object.

create(store: Store) => Metrics

A function for performing the creation steps of the Metrics object for the Store, plus any additional steps such as adding definitions or listeners, and returning it.

createDeps?DependencyList

An optional array of dependencies for the create function, which, if any change, result in its rerun. This parameter defaults to an empty array.

returnsMetrics

A reference to the Metrics object.

It is possible to create a Metrics object outside of the React app with the regular createMetrics function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Metrics object being created every time the app renders or re-renders, the useCreateMetrics hook wraps the creation in a memoization.

The useCreateMetrics hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.

If your create function contains other dependencies, the changing of which should also cause the Metrics object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.

This hook ensures the Metrics object is destroyed whenever a new one is created or the component is unmounted.

Examples

This example creates a Metrics object at the top level of a React application. Even though the App component is rendered twice, the Metrics object creation only occurs once by default.

const App = () => {
  const store = useCreateStore(() =>
    createStore().setTable('species', {dog: {price: 5}, cat: {price: 4}}),
  );
  const metrics = useCreateMetrics(store, (store) => {
    console.log('Metrics created');
    return createMetrics(store).setMetricDefinition(
      'speciesCount',
      'species',
    );
  });
  return <span>{metrics.getMetric('speciesCount')}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
// -> 'Metrics created'

ReactDOM.render(<App />, app);
// No second Metrics creation

console.log(app.innerHTML);
// -> '<span>2</span>'

This example creates a Metrics object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateMetrics hook takes the tableToCount prop as a dependency, and so the Metrics object is created again on the second render.

const App = ({tableToCount}) => {
  const store = useCreateStore(() =>
    createStore()
      .setTable('pets', {fido: {species: 'dog'}})
      .setTable('species', {dog: {price: 5}, cat: {price: 4}}),
  );
  const metrics = useCreateMetrics(
    store,
    (store) => {
      console.log(`Count created for ${tableToCount} table`);
      return createMetrics(store).setMetricDefinition(
        'tableCount',
        tableToCount,
      );
    },
    [tableToCount],
  );
  return <span>{metrics.getMetric('tableCount')}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App tableToCount="pets" />, app);
// -> 'Count created for pets table'

console.log(app.innerHTML);
// -> '<span>1</span>'

ReactDOM.render(<App tableToCount="species" />, app);
// -> 'Count created for species table'

console.log(app.innerHTML);
// -> '<span>2</span>'
useMetric

The useMetric hook gets the current value of a Metric, and registers a listener so that any changes to that result will cause a re-render.

useMetric(
  metricId: string,
  metricsOrMetricsId?: MetricsOrMetricsId,
): number | undefined
TypeDescription
metricIdstring

The Id of the Metric.

metricsOrMetricsId?MetricsOrMetricsId

The Metrics object to be accessed: omit for the default context Metrics object, provide an Id for a named context Metrics object, or provide an explicit reference.

returnsnumber | undefined

The numeric value of the Metric, or `undefined`.

A Provider component is used to wrap part of an application in a context, and it can contain a default Metrics object or a set of Metrics objects named by Id. The useMetric hook lets you indicate which Metrics object to get data for: omit the optional final parameter for the default context Metrics object, provide an Id for a named context Metrics object, or provide a Metrics object explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Metric will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Metrics object outside the application, which is used in the useMetric hook by reference. A change to the Metric re-renders the component.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const App = () => <span>{useMetric('highestPrice', metrics)}</span>;

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>5</span>'

store.setCell('species', 'horse', 'price', 20);
console.log(app.innerHTML);
// -> '<span>20</span>'

This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetric hook.

const App = ({metrics}) => (
  <Provider metrics={metrics}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useMetric('highestPrice')}</span>;

const metrics = createMetrics(
  createStore().setTable('species', {
    dog: {price: 5},
    cat: {price: 4},
    worm: {price: 1},
  }),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');

const app = document.createElement('div');
ReactDOM.render(<App metrics={metrics} />, app);
console.log(app.innerHTML);
// -> '<span>5</span>'

This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetric hook.

const App = ({metrics}) => (
  <Provider metricsById={{petMetrics: metrics}}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useMetric('highestPrice', 'petMetrics')}</span>;

const metrics = createMetrics(
  createStore().setTable('species', {
    dog: {price: 5},
    cat: {price: 4},
    worm: {price: 1},
  }),
).setMetricDefinition('highestPrice', 'species', 'max', 'price');

const app = document.createElement('div');
ReactDOM.render(<App metrics={metrics} />, app);
console.log(app.innerHTML);
// -> '<span>5</span>'
useMetricListener

The useMetricListener hook registers a listener function with the Metrics object that will be called whenever the value of a specified Metric changes.

useMetricListener(
  metricId: IdOrNull,
  listener: MetricListener,
  listenerDeps?: DependencyList,
  metricsOrMetricsId?: MetricsOrMetricsId,
): void
TypeDescription
metricIdIdOrNull

The Id of the Metric to listen to, or null as a wildcard.

listenerMetricListener

The function that will be called whenever the Metric changes.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

metricsOrMetricsId?MetricsOrMetricsId

The Metrics object to register the listener with: omit for the default context Metrics object, provide an Id for a named context Metrics object, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useMetric hook).

You can either listen to a single Metric (by specifying the Metric Id as the method's first parameter), or changes to any Metric (by providing a null wildcard).

Unlike the addMetricListener method, which returns a listener Id and requires you to remove it manually, the useMetricListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Metrics object, will be deleted.

Example

This example uses the useMetricListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Metrics object.

const App = ({metrics}) => (
  <Provider metrics={metrics}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useMetricListener('highestPrice', () => console.log('Metric changed'));
  return <span>App</span>;
};

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

const app = document.createElement('div');
ReactDOM.render(<App metrics={metrics} />, app);
console.log(metrics.getListenerStats().metric);
// -> 1

store.setCell('species', 'horse', 'price', 20);
// -> 'Metric changed'

ReactDOM.unmountComponentAtNode(app);
console.log(metrics.getListenerStats().metric);
// -> 0
useMetrics

The useMetrics hook is used to get a reference to a Metrics object from within a Provider component context.

useMetrics(id?: string): Metrics | undefined
TypeDescription
id?string

An optional Id for accessing a Metrics object that was named with an Id in the Provider.

returnsMetrics | undefined

A reference to the Metrics object (or `undefined` if not within a Provider context, or if the requested Metrics object does not exist)

A Provider component is used to wrap part of an application in a context. It can contain a default Metrics object (or a set of Metrics objects named by Id) that can be easily accessed without having to be passed down as props through every component.

The useMetrics hook lets you either get a reference to the default Metrics object (when called without an parameter), or one of the Metrics objects that are named by Id (when called with an Id parameter).

Examples

This example creates a Provider context into which a default Metrics object is provided. A component within it then uses the useMetrics hook to get a reference to the Metrics object again, without the need to have it passed as a prop.

const App = ({metrics}) => (
  <Provider metrics={metrics}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useMetrics().getListenerStats().metric}</span>;

const metrics = createMetrics(createStore());
const app = document.createElement('div');
ReactDOM.render(<App metrics={metrics} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'

This example creates a Provider context into which a Metrics object is provided, named by Id. A component within it then uses the useMetrics hook with that Id to get a reference to the Metrics object again, without the need to have it passed as a prop.

const App = ({metrics}) => (
  <Provider metricsById={{petStore: metrics}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{useMetrics('petStore').getListenerStats().metric}</span>
);

const metrics = createMetrics(createStore());
const app = document.createElement('div');
ReactDOM.render(<App metrics={metrics} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'

Persister hooks

This is the collection of persister hooks within the ui-react module. There is only one function, useCreatePersister.

useCreatePersister

The useCreatePersister hook is used to create a Persister within a React application along with convenient memoization and callbacks.

useCreatePersister(
  store: Store,
  create: (store: Store) => Persister,
  createDeps?: DependencyList,
  then?: (persister: Persister) => Promise<void>,
  thenDeps?: DependencyList,
): Persister
TypeDescription
storeStore

A reference to the Store for which to create a new Persister object.

create(store: Store) => Persister

A function for performing the creation steps of the Persister object for the Store.

createDeps?DependencyList

An optional array of dependencies for the create function, which, if any change, result in its rerun. This parameter defaults to an empty array.

then?(persister: Persister) => Promise<void>

An optional callback for performing asynchronous post-creation steps on the Persister, such as starting automatic loading and saving.

thenDeps?DependencyList

An optional array of dependencies for the then callback, which, if any change, result in its rerun. This parameter defaults to an empty array.

returnsPersister

A reference to the Persister.

It is possible to create a Persister outside of the React app with the regular createPersister function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Persister being created every time the app renders or re-renders, the useCreatePersister hook wraps the creation in a memoization, and provides a second callback so that you can configure the Persister, once, and asynchronously, when it is created.

If your create function (the second parameter to the hook) contains dependencies, the changing of which should cause the Persister to be recreated, you can provide them in an array in the third parameter, just as you would for any React hook with dependencies. The Store passed in as the first parameter of this hook is used as a dependency by default.

A second then callback can be provided as the fourth parameter. This is called after the creation, and, importantly, can be asynchronous, so that you can configure the Persister with the startAutoLoad method and startAutoSave method, for example. If this callback contains dependencies, the changing of which should cause the Persister to be reconfigured, you can provide them in an array in the fifth parameter. The Persister itself is used as a dependency by default.

This hook ensures the Persister object is destroyed whenever a new one is created or the component is unmounted.

Examples

This example creates a Persister at the top level of a React application. Even though the App component is rendered twice, the Persister creation only occurs once by default.

const App = () => {
  const store = useCreateStore(createStore);
  const persister = useCreatePersister(
    store,
    (store) => {
      console.log('Persister created');
      return createSessionPersister(store, 'pets');
    },
    [],
    async (persister) => {
      await persister.startAutoLoad();
      await persister.startAutoSave();
    },
  );
  return <span>{JSON.stringify(useTables(store))}</span>;
};

sessionStorage.setItem('pets', '{"pets":{"fido":{"species":"dog"}}}');

const app = document.createElement('div');
ReactDOM.render(<App />, app);
// -> 'Persister created'

// ...
ReactDOM.render(<App />, app);
// No second Persister creation

console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'

ReactDOM.unmountComponentAtNode(app);

This example creates a Persister at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreatePersister hook takes the sessionKey prop as a dependency, and so the Persister object is created again on the second render.

const App = ({sessionKey}) => {
  const store = useCreateStore(createStore);
  const persister = useCreatePersister(
    store,
    (store) => {
      console.log(`Persister created for session key ${sessionKey}`);
      return createSessionPersister(store, sessionKey);
    },
    [sessionKey],
    async (persister) => {
      await persister.startAutoLoad();
      await persister.startAutoSave();
    },
  );
  return <span>{JSON.stringify(useTables(store))}</span>;
};

sessionStorage.setItem('fidoStore', '{"pets":{"fido":{"species":"dog"}}}');
sessionStorage.setItem('cujoStore', '{"pets":{"cujo":{"species":"dog"}}}');

const app = document.createElement('div');
ReactDOM.render(<App sessionKey="fidoStore" />, app);
// -> 'Persister created for session key fidoStore'

// ...
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"fido\":{\"species\":\"dog\"}}}</span>'

ReactDOM.render(<App sessionKey="cujoStore" />, app);
// -> 'Persister created for session key cujoStore'

// ...
console.log(app.innerHTML);
// -> '<span>{\"pets\":{\"cujo\":{\"species\":\"dog\"}}}</span>'

ReactDOM.unmountComponentAtNode(app);

Relationships hooks

This is the collection of relationships hooks within the ui-react module. There are 8 relationships hooks in total.

useLinkedRowIds

The useLinkedRowIds hook gets the linked Row Ids for a given Row in a linked list Relationship, and registers a listener so that any changes to that result will cause a re-render.

useLinkedRowIds(
  relationshipId: string,
  firstRowId: string,
  relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Ids
TypeDescription
relationshipIdstring

The Id of the Relationship.

firstRowIdstring

The Id of the first Row in the linked list Relationship.

relationshipsOrRelationshipsId?RelationshipsOrRelationshipsId

The Relationships object to be accessed: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

returnsIds

The linked Row Ids in the Relationship.

A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useLinkedRowIds hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.

When first rendered, this hook will create a listener so that changes to the linked Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Relationships object outside the application, which is used in the useLinkedRowIds hook by reference. A change to the linked Row Ids re-renders the component.

const store = createStore().setTable('pets', {
  fido: {species: 'dog', next: 'felix'},
  felix: {species: 'cat', next: 'cujo'},
  cujo: {species: 'dog'},
});
const relationships = createRelationships(store).setRelationshipDefinition(
  'petSequence',
  'pets',
  'pets',
  'next',
);
const App = () => (
  <span>
    {JSON.stringify(useLinkedRowIds('petSequence', 'fido', relationships))}
  </span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'

store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo","toto"]</span>'

This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLinkedRowIds hook.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useLinkedRowIds('petSequence', 'fido'))}</span>
);

const relationships = createRelationships(
  createStore().setTable('pets', {
    fido: {species: 'dog', next: 'felix'},
    felix: {species: 'cat', next: 'cujo'},
    cujo: {species: 'dog'},
  }),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'

This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLinkedRowIds hook.

const App = ({relationships}) => (
  <Provider relationshipsById={{petRelationships: relationships}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>
    {JSON.stringify(
      useLinkedRowIds('petSequence', 'fido', 'petRelationships'),
    )}
  </span>
);

const relationships = createRelationships(
  createStore().setTable('pets', {
    fido: {species: 'dog', next: 'felix'},
    felix: {species: 'cat', next: 'cujo'},
    cujo: {species: 'dog'},
  }),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<span>["fido","felix","cujo"]</span>'
useLinkedRowIdsListener

The useLinkedRowIdsListener hook registers a listener function with the Relationships object that will be called whenever the linked Row Ids in a Relationship change.

useLinkedRowIdsListener(
  relationshipId: string,
  firstRowId: string,
  listener: LinkedRowIdsListener,
  listenerDeps?: DependencyList,
  relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void
TypeDescription
relationshipIdstring

The Id of the Relationship to listen to.

firstRowIdstring

The Id of the first Row of the linked list to listen to.

listenerLinkedRowIdsListener

The function that will be called whenever the linked Row Ids change.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

relationshipsOrRelationshipsId?RelationshipsOrRelationshipsId

The Relationships object to register the listener with: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useLinkedRowsId hook).

Unlike other listener registration methods, you cannot provide null wildcards for the first two parameters of the useLinkedRowIdsListener method. This prevents the prohibitive expense of tracking all the possible linked lists (and partial linked lists within them) in a Store.

Unlike the addLinkedRowsIdListener method, which returns a listener Id and requires you to remove it manually, the useLinkedRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.

Example

This example uses the useLinkedRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useLinkedRowIdsListener('petSequence', 'fido', () =>
    console.log('Linked Row Ids changed'),
  );
  return <span>App</span>;
};

const store = createStore().setTable('pets', {
  fido: {species: 'dog', next: 'felix'},
  felix: {species: 'cat', next: 'cujo'},
  cujo: {species: 'dog'},
});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSequence',
  'pets',
  'pets',
  'next',
);

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(relationships.getListenerStats().linkedRowIds);
// -> 1

store.setRow('pets', 'toto', {species: 'dog'});
store.setCell('pets', 'cujo', 'next', 'toto');
// -> 'Linked Row Ids changed'

ReactDOM.unmountComponentAtNode(app);
console.log(relationships.getListenerStats().linkedRowIds);
// -> 0
useLocalRowIds

The useLocalRowIds hook gets the local Row Ids for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.

useLocalRowIds(
  relationshipId: string,
  remoteRowId: string,
  relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Ids
TypeDescription
relationshipIdstring

The Id of the Relationship.

remoteRowIdstring

The Id of the remote Row in the Relationship.

relationshipsOrRelationshipsId?RelationshipsOrRelationshipsId

The Relationships object to be accessed: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

returnsIds

The local Row Ids in the Relationship, or an empty array.

A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useLocalRowIds hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.

When first rendered, this hook will create a listener so that changes to the local Row Id will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Relationships object outside the application, which is used in the useLocalRowIds hook by reference. A change to the local Row Ids re-renders the component.

const store = createStore()
  .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
  .setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);
const App = () => (
  <span>
    {JSON.stringify(useLocalRowIds('petSpecies', 'dog', relationships))}
  </span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'

store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<span>["fido","cujo","toto"]</span>'

This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLocalRowIds hook.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useLocalRowIds('petSpecies', 'dog'))}</span>
);

const relationships = createRelationships(
  createStore()
    .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
    .setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'

This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useLocalRowIds hook.

const App = ({relationships}) => (
  <Provider relationshipsById={{petRelationships: relationships}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>
    {JSON.stringify(
      useLocalRowIds('petSpecies', 'dog', 'petRelationships'),
    )}
  </span>
);

const relationships = createRelationships(
  createStore()
    .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
    .setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
useLocalRowIdsListener

The useLocalRowIdsListener hook registers a listener function with the Relationships object that will be called whenever the local Row Ids in a Relationship change.

useLocalRowIdsListener(
  relationshipId: IdOrNull,
  remoteRowId: IdOrNull,
  listener: LocalRowIdsListener,
  listenerDeps?: DependencyList,
  relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void
TypeDescription
relationshipIdIdOrNull

The Id of the Relationship to listen to, or null as a wildcard.

remoteRowIdIdOrNull

The Id of the remote Row to listen to, or null as a wildcard.

listenerLocalRowIdsListener

The function that will be called whenever the local Row Ids change.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

relationshipsOrRelationshipsId?RelationshipsOrRelationshipsId

The Relationships object to register the listener with: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useLocalRowsId hook).

You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).

Both, either, or neither of the relationshipId and remoteRowId parameters can be wildcarded with null. You can listen to a specific remote Row in a specific Relationship, any remote Row in a specific Relationship, a specific remote Row in any Relationship, or any remote Row in any Relationship.

Unlike the addLocalRowsIdListener method, which returns a listener Id and requires you to remove it manually, the useLocalRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.

Example

This example uses the useLocalRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useLocalRowIdsListener('petSpecies', 'dog', () =>
    console.log('Local Row Ids changed'),
  );
  return <span>App</span>;
};

const store = createStore()
  .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
  .setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(relationships.getListenerStats().localRowIds);
// -> 1

store.setRow('pets', 'toto', {species: 'dog'});
// -> 'Local Row Ids changed'

ReactDOM.unmountComponentAtNode(app);
console.log(relationships.getListenerStats().localRowIds);
// -> 0
useRemoteRowId

The useRemoteRowId hook gets the remote Row Id for a given local Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.

useRemoteRowId(
  relationshipId: string,
  localRowId: string,
  relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): Id | undefined
TypeDescription
relationshipIdstring

The Id of the Relationship.

localRowIdstring

The Id of the local Row in the Relationship.

relationshipsOrRelationshipsId?RelationshipsOrRelationshipsId

The Relationships object to be accessed: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

returnsId | undefined

The remote Row Id in the Relationship, or `undefined`.

A Provider component is used to wrap part of an application in a context, and it can contain a default Relationships object or a set of Relationships objects named by Id. The useRemoteRowId hook lets you indicate which Relationships object to get data for: omit the optional final parameter for the default context Relationships object, provide an Id for a named context Relationships object, or provide a Relationships object explicitly by reference.

When first rendered, this hook will create a listener so that changes to the remote Row Id will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Relationships object outside the application, which is used in the useRemoteRowId hook by reference. A change to the remote Row Id re-renders the component.

const store = createStore()
  .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
  .setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);
const App = () => (
  <span>{useRemoteRowId('petSpecies', 'cujo', relationships)}</span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>dog</span>'

store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>wolf</span>'

This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRemoteRowId hook.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useRemoteRowId('petSpecies', 'cujo')}</span>;

const relationships = createRelationships(
  createStore()
    .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
    .setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<span>dog</span>'

This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRemoteRowId hook.

const App = ({relationships}) => (
  <Provider relationshipsById={{petRelationships: relationships}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{useRemoteRowId('petSpecies', 'cujo', 'petRelationships')}</span>
);

const relationships = createRelationships(
  createStore()
    .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
    .setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<span>dog</span>'
useRemoteRowIdListener

The useRemoteRowIdListener hook registers a listener function with the Relationships object that will be called whenever a remote Row Id in a Relationship changes.

useRemoteRowIdListener(
  relationshipId: IdOrNull,
  localRowId: IdOrNull,
  listener: RemoteRowIdListener,
  listenerDeps?: DependencyList,
  relationshipsOrRelationshipsId?: RelationshipsOrRelationshipsId,
): void
TypeDescription
relationshipIdIdOrNull

The Id of the Relationship to listen to, or null as a wildcard.

localRowIdIdOrNull

The Id of the local Row to listen to, or null as a wildcard.

listenerRemoteRowIdListener

The function that will be called whenever the remote Row Id changes.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

relationshipsOrRelationshipsId?RelationshipsOrRelationshipsId

The Relationships object to register the listener with: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRemoteRowId hook).

You can either listen to a single local Row (by specifying the Relationship Id and local Row Id as the method's first two parameters), or changes to any local Row (by providing a null wildcards).

Both, either, or neither of the relationshipId and localRowId parameters can be wildcarded with null. You can listen to a specific local Row in a specific Relationship, any local Row in a specific Relationship, a specific local Row in any Relationship, or any local Row in any Relationship.

Unlike the addRemoteRowIdListener method, which returns a listener Id and requires you to remove it manually, the useRemoteRowIdListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Indexes object will be deleted.

Example

This example uses the useRemoteRowIdListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Relationships object.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useRemoteRowIdListener('petSpecies', 'cujo', () =>
    console.log('Remote Row Id changed'),
  );
  return <span>App</span>;
};

const store = createStore()
  .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
  .setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store);
relationships.setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(relationships.getListenerStats().remoteRowId);
// -> 1

store.setCell('pets', 'cujo', 'species', 'wolf');
// -> 'Remote Row Id changed'

ReactDOM.unmountComponentAtNode(app);
console.log(relationships.getListenerStats().remoteRowId);
// -> 0
useCreateRelationships

The useCreateRelationships hook is used to create a Relationships object within a React application with convenient memoization.

useCreateRelationships(
  store: Store,
  create: (store: Store) => Relationships,
  createDeps?: DependencyList,
): Relationships
TypeDescription
storeStore

A reference to the Store for which to create a new Relationships object.

create(store: Store) => Relationships

An optional callback for performing post-creation steps on the Relationships object, such as adding definitions or listeners.

createDeps?DependencyList

An optional array of dependencies for the create function, which, if any change, result in its rerun. This parameter defaults to an empty array.

returnsRelationships

A reference to the Relationships object.

It is possible to create a Relationships object outside of the React app with the regular createRelationships function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Relationships object being created every time the app renders or re-renders, the useCreateRelationships hook wraps the creation in a memoization.

The useCreateRelationships hook is a very thin wrapper around the React useMemo hook, defaulting to the provided Store as its dependency, so that by default, the creation only occurs once per Store.

If your create function contains other dependencies, the changing of which should also cause the Relationships object to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.

This hook ensures the Relationships object is destroyed whenever a new one is created or the component is unmounted.

Examples

This example creates a Relationships object at the top level of a React application. Even though the App component is rendered twice, the Relationships object creation only occurs once by default.

const App = () => {
  const store = useCreateStore(() =>
    createStore()
      .setTable('pets', {
        fido: {species: 'dog'},
        felix: {species: 'cat'},
        cujo: {species: 'dog'},
      })
      .setTable('species', {dog: {price: 5}, cat: {price: 4}}),
  );
  const relationships = useCreateRelationships(store, (store) => {
    console.log('Relationships created');
    return createRelationships(store).setRelationshipDefinition(
      'petSpecies',
      'pets',
      'species',
      'species',
    );
  });
  return <span>{relationships.getRemoteRowId('petSpecies', 'fido')}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
// -> 'Relationships created'

ReactDOM.render(<App />, app);
// No second Relationships creation

console.log(app.innerHTML);
// -> '<span>dog</span>'

This example creates a Relationships object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateRelationships hook takes the remoteTableAndCellToLink prop as a dependency, and so the Relationships object is created again on the second render.

const App = ({remoteTableAndCellToLink}) => {
  const store = useCreateStore(() =>
    createStore()
      .setTable('pets', {
        fido: {species: 'dog', color: 'brown'},
        felix: {species: 'cat', color: 'black'},
        cujo: {species: 'dog', color: 'brown'},
      })
      .setTable('species', {dog: {price: 5}, cat: {price: 4}})
      .setTable('color', {brown: {discount: 0.1}, black: {discount: 0}}),
  );
  const relationships = useCreateRelationships(
    store,
    (store) => {
      console.log(`Relationship created to ${remoteTableAndCellToLink}`);
      return createRelationships(store).setRelationshipDefinition(
        'cellLinked',
        'pets',
        remoteTableAndCellToLink,
        remoteTableAndCellToLink,
      );
    },
    [remoteTableAndCellToLink],
  );
  return <span>{relationships.getRemoteRowId('cellLinked', 'fido')}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App remoteTableAndCellToLink="species" />, app);
// -> 'Relationship created to species'

console.log(app.innerHTML);
// -> '<span>dog</span>'

ReactDOM.render(<App remoteTableAndCellToLink="color" />, app);
// -> 'Relationship created to color'

console.log(app.innerHTML);
// -> '<span>brown</span>'
useRelationships

The useRelationships hook is used to get a reference to a Relationships object from within a Provider component context.

useRelationships(id?: string): Relationships | undefined
TypeDescription
id?string

An optional Id for accessing a Relationships object that was named with an Id in the Provider.

returnsRelationships | undefined

A reference to the Relationships object (or `undefined` if not within a Provider context, or if the requested Relationships object does not exist)

A Provider component is used to wrap part of an application in a context. It can contain a default Relationships object (or a set of Relationships objects named by Id) that can be easily accessed without having to be passed down as props through every component.

The useRelationships hook lets you either get a reference to the default Relationships object (when called without an parameter), or one of the Relationships objects that are named by Id (when called with an Id parameter).

Examples

This example creates a Provider context into which a default Relationships object is provided. A component within it then uses the useRelationships hook to get a reference to the Relationships object again, without the need to have it passed as a prop.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{useRelationships().getListenerStats().remoteRowId}</span>
);

const relationships = createRelationships(createStore());
const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'

This example creates a Provider context into which a Relationships object is provided, named by Id. A component within it then uses the useRelationships hook with that Id to get a reference to the Relationships object again, without the need to have it passed as a prop.

const App = ({relationships}) => (
  <Provider relationshipsById={{petStore: relationships}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>
    {useRelationships('petStore').getListenerStats().remoteRowId}
  </span>
);

const relationships = createRelationships(createStore());
const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'

Store hooks

This is the collection of store hooks within the ui-react module. There are 26 store hooks in total.

useCreateStore

The useCreateStore hook is used to create a Store within a React application with convenient memoization.

useCreateStore(
  create: () => Store,
  createDeps?: DependencyList,
): Store
TypeDescription
create() => Store

A function for performing the creation of the Store, plus any additional steps such as adding data or listeners, and returning it.

createDeps?DependencyList

An optional array of dependencies for the create function, which, if any change, result in its rerun. This parameter defaults to an empty array.

returnsStore

A reference to the Store.

It is possible to create a Store outside of the React app with the regular createStore function and pass it in, but you may prefer to create it within the app, perhaps inside the top-level component. To defend against a new Store being created every time the app renders or re-renders, the useCreateStore hook wraps the creation in a memoization.

The useCreateStore hook is a very thin wrapper around the React useMemo hook, defaulting to an empty array for its dependencies, so that by default, the creation only occurs once.

If your create function contains other dependencies, the changing of which should cause the Store to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.

Examples

This example creates an empty Store at the top level of a React application. Even though the App component is rendered twice, the Store creation only occurs once by default.

const App = () => {
  const store = useCreateStore(() => {
    console.log('Store created');
    return createStore().setTables({pets: {fido: {species: 'dog'}}});
  });
  return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
// -> 'Store created'

ReactDOM.render(<App />, app);
// No second Store creation

console.log(app.innerHTML);
// -> '<span>dog</span>'

This example creates an empty Store at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateStore hook takes the fidoSpecies prop as a dependency, and so the Store is created again on the second render.

const App = ({fidoSpecies}) => {
  const store = useCreateStore(() => {
    console.log(`Store created for fido as ${fidoSpecies}`);
    return createStore().setTables({pets: {fido: {species: fidoSpecies}}});
  }, [fidoSpecies]);
  return <span>{store.getCell('pets', 'fido', 'species')}</span>;
};

const app = document.createElement('div');
ReactDOM.render(<App fidoSpecies="dog" />, app);
// -> 'Store created for fido as dog'

console.log(app.innerHTML);
// -> '<span>dog</span>'

ReactDOM.render(<App fidoSpecies="cat" />, app);
// -> 'Store created for fido as cat'

console.log(app.innerHTML);
// -> '<span>cat</span>'
useStore

The useStore hook is used to get a reference to a Store from within a Provider component context.

useStore(id?: string): Store | undefined
TypeDescription
id?string

An optional Id for accessing a Store that was named with an Id in the Provider.

returnsStore | undefined

A reference to the Store (or `undefined` if not within a Provider context, or if the requested Store does not exist)

A Provider component is used to wrap part of an application in a context. It can contain a default Store (or a set of Store objects named by Id) that can be easily accessed without having to be passed down as props through every component.

The useStore hook lets you either get a reference to the default Store (when called without an parameter), or one of the Store objects that are named by Id (when called with an Id parameter).

Examples

This example creates a Provider context into which a default Store is provided. A component within it then uses the useStore hook to get a reference to the Store again, without the need to have it passed as a prop.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useStore().getListenerStats().tables}</span>;

const store = createStore();
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'

This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useStore hook with that Id to get a reference to the Store again, without the need to have it passed as a prop.

const App = ({store}) => (
  <Provider storesById={{petStore: store}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{useStore('petStore').getListenerStats().tables}</span>
);

const store = createStore();
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'
useDelTablesCallback

The useDelTablesCallback hook returns a callback that can be used to remove all of the data in a Store.

useDelTablesCallback(
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store) => void,
  thenDeps?: DependencyList,
): Callback
TypeDescription
storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store) => void

A function which is called after the deletion, with a reference to the Store.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsCallback

A callback for subsequent use.

This hook is useful, for example, when creating an event handler that will delete data in a Store.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.

The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useDelTablesCallback hook to create an event handler which deletes from the Store when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
  const handleClick = useDelTablesCallback(store, () =>
    console.log('Deleted'),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useTables(store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'

console.log(span.innerHTML);
// -> '{}'
useSetTablesCallback

The useSetTablesCallback hook returns a parameterized callback that can be used to set the entire data of a Store.

useSetTablesCallback<Parameter>(
  getTables: (parameter: Parameter, store: Store) => Tables,
  getTablesDeps?: DependencyList,
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store, tables: Tables) => void,
  thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
TypeDescription
getTables(parameter: Parameter, store: Store) => Tables

A function which returns the Tables object that will be used to update the Store, based on the parameter the callback will receive (and which is most likely a DOM event).

getTablesDeps?DependencyList

An optional array of dependencies for the getTables function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store, tables: Tables) => void

A function which is called after the mutation, with a reference to the Store and the Tables used in the update.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsParameterizedCallback<Parameter>

A parameterized callback for subsequent use.

This hook is useful, for example, when creating an event handler that will mutate the data in Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.

The first parameter is a function which will produce the Tables object that will then be used to update the Store in the callback.

If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional second parameter, just as you would for any React hook with dependencies.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.

The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useSetTablesCallback hook to create an event handler which updates the Store when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
  const handleClick = useSetTablesCallback(
    (e) => ({pets: {nemo: {species: 'fish', bubbles: e.bubbles}}}),
    [],
    store,
    (store, tables) => console.log(`Updated: ${JSON.stringify(tables)}`),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useTables(store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"pets":{"nemo":{"species":"fish","bubbles":true}}}'

console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish","bubbles":true}}}'
useTables

The useTables hook returns a Tables object containing the entire data of a Store, and registers a listener so that any changes to that result will cause a re-render.

useTables(storeOrStoreId?: StoreOrStoreId): Tables
TypeDescription
storeOrStoreId?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsTables

A Tables object containing the entire data of the Store.

A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTables hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Tables will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Store outside the application, which is used in the useTables hook by reference. A change to the data in the Store re-renders the component.

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTables(store))}</span>;

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'

store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"walnut"}}}</span>'

This example creates a Provider context into which a default Store is provided. A component within it then uses the useTables hook.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useTables())}</span>;

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'

This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTables hook.

const App = ({store}) => (
  <Provider storesById={{petStore: store}}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useTables('petStore'))}</span>;

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>{"pets":{"fido":{"color":"brown"}}}</span>'
useTablesListener

The useTablesListener hook registers a listener function with a Store that will be called whenever data in it changes.

useTablesListener(
  listener: TablesListener,
  listenerDeps?: DependencyList,
  mutator?: boolean,
  storeOrStoreId?: StoreOrStoreId,
): void
TypeDescription
listenerTablesListener

The function that will be called whenever data in the Store changes.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

storeOrStoreId?StoreOrStoreId

The Store to register the listener with: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTables hook).

Unlike the addTablesListener method, which returns a listener Id and requires you to remove it manually, the useTablesListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.

Example

This example uses the useTablesListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useTablesListener(() => console.log('Tables changed'));
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(store.getListenerStats().tables);
// -> 1

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Tables changed'

ReactDOM.unmountComponentAtNode(app);
console.log(store.getListenerStats().tables);
// -> 0
useTableIds

The useTableIds hook returns the Ids of every Table in a Store, and registers a listener so that any changes to that result will cause a re-render. forward A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTableIds hook lets you indicate which Store to get data for: omit the optional parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.

useTableIds(storeOrStoreId?: StoreOrStoreId): Ids
TypeDescription
storeOrStoreId?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsIds

An array of the Ids of every Table in the Store.

When first rendered, this hook will create a listener so that changes to the Table Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Store outside the application, which is used in the useTableIds hook by reference. A change to the data in the Store re-renders the component.

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTableIds(store))}</span>;

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'

store.setCell('species', 'dog', 'price', 5);
console.log(app.innerHTML);
// -> '<span>["pets","species"]</span>'

This example creates a Provider context into which a default Store is provided. A component within it then uses the useTableIds hook.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useTableIds())}</span>;

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'

This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTableIds hook.

const App = ({store}) => (
  <Provider storesById={{petStore: store}}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useTableIds('petStore'))}</span>;

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>["pets"]</span>'
useTableIdsListener

The useTableIdsListener hook registers a listener function with a Store that will be called whenever the Table Ids in it change.

useTableIdsListener(
  listener: TableIdsListener,
  listenerDeps?: DependencyList,
  mutator?: boolean,
  storeOrStoreId?: StoreOrStoreId,
): void
TypeDescription
listenerTableIdsListener

The function that will be called whenever the Table Ids in the Store change.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

storeOrStoreId?StoreOrStoreId

The Store to register the listener with: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTableIds hook).

Unlike the addTableIdsListener method, which returns a listener Id and requires you to remove it manually, the useTableIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.

Example

This example uses the useTableIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useTableIdsListener(() => console.log('Table Ids changed'));
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(store.getListenerStats().tableIds);
// -> 1

store.setTable('species', {dog: {price: 5}});
// -> 'Table Ids changed'

ReactDOM.unmountComponentAtNode(app);
console.log(store.getListenerStats().tableIds);
// -> 0
useDelTableCallback

The useDelTableCallback hook returns a callback that can be used to remove a single Table from a Store.

useDelTableCallback(
  tableId: string,
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store) => void,
  thenDeps?: DependencyList,
): Callback
TypeDescription
tableIdstring

The Id of the Table in the Store.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store) => void

A function which is called after the deletion, with a reference to the Store.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsCallback

A callback for subsequent use.

This hook is useful, for example, when creating an event handler that will delete data in a Store.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.

The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useDelTableCallback hook to create an event handler which deletes from the Store when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
  const handleClick = useDelTableCallback('pets', store, () =>
    console.log('Deleted'),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useTables(store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'

console.log(span.innerHTML);
// -> '{}'
useSetTableCallback

The useSetTableCallback hook returns a parameterized callback that can be used to set the entire data of a single Table in a Store.

useSetTableCallback<Parameter>(
  tableId: string,
  getTable: (parameter: Parameter, store: Store) => Table,
  getTableDeps?: DependencyList,
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store, table: Table) => void,
  thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
TypeDescription
tableIdstring

The Id of the Table in the Store to set.

getTable(parameter: Parameter, store: Store) => Table

A function which returns the Table object that will be used to update the Store, based on the parameter the callback will receive (and which is most likely a DOM event).

getTableDeps?DependencyList

An optional array of dependencies for the getTable function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store, table: Table) => void

A function which is called after the mutation, with a reference to the Store and the Table used in the update.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsParameterizedCallback<Parameter>

A parameterized callback for subsequent use.

This hook is useful, for example, when creating an event handler that will mutate the data in Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.

The second parameter is a function which will produce the Table object that will then be used to update the Store in the callback.

If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.

The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useSetTableCallback hook to create an event handler which updates the Store when the span element is clicked.

const store = createStore().setTable('pets', {nemo: {species: 'fish'}});
const App = () => {
  const handleClick = useSetTableCallback(
    'pets',
    (e) => ({nemo: {species: 'fish', bubbles: e.bubbles}}),
    [],
    store,
    (store, table) => console.log(`Updated: ${JSON.stringify(table)}`),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useTable('pets', store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish"}}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"nemo":{"species":"fish","bubbles":true}}'

console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish","bubbles":true}}'
useTable

The useTable hook returns an object containing the entire data of a single Table in a Store, and registers a listener so that any changes to that result will cause a re-render.

useTable(
  tableId: string,
  storeOrStoreId?: StoreOrStoreId,
): Table
TypeDescription
tableIdstring

The Id of the Table in the Store.

storeOrStoreId?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsTable

An object containing the entire data of the Table.

A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useTable hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Table will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Store outside the application, which is used in the useTable hook by reference. A change to the data in the Store re-renders the component.

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useTable('pets', store))}</span>;

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'

store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"}}</span>'

This example creates a Provider context into which a default Store is provided. A component within it then uses the useTable hook.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useTable('pets'))}</span>;

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'

This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useTable hook.

const App = ({store}) => (
  <Provider storesById={{petStore: store}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useTable('pets', 'petStore'))}</span>
);

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"}}</span>'
useTableListener

The useTableListener hook registers a listener function with a Store that will be called whenever data in a Table changes.

useTableListener(
  tableId: IdOrNull,
  listener: TableListener,
  listenerDeps?: DependencyList,
  mutator?: boolean,
  storeOrStoreId?: StoreOrStoreId,
): void
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

listenerTableListener

The function that will be called whenever data in the Table changes.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

storeOrStoreId?StoreOrStoreId

The Store to register the listener with: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useTable hook).

You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing a null wildcard).

Unlike the addTableListener method, which returns a listener Id and requires you to remove it manually, the useTableListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.

Example

This example uses the useTableListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useTableListener('pets', () => console.log('Table changed'));
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(store.getListenerStats().table);
// -> 1

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Table changed'

ReactDOM.unmountComponentAtNode(app);
console.log(store.getListenerStats().table);
// -> 0
useRowIds

The useRowIds hook returns the Ids of every Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.

useRowIds(
  tableId: string,
  storeOrStoreId?: StoreOrStoreId,
): Ids
TypeDescription
tableIdstring

The Id of the Table in the Store.

storeOrStoreId?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsIds

An array of the Ids of every Row in the Table.

A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRowIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Row Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Store outside the application, which is used in the useRowIds hook by reference. A change to the data in the Store re-renders the component.

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{JSON.stringify(useRowIds('pets', store))}</span>;

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'

store.setCell('pets', 'felix', 'color', 'black');
console.log(app.innerHTML);
// -> '<span>["fido","felix"]</span>'

This example creates a Provider context into which a default Store is provided. A component within it then uses the useRowIds hook.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useRowIds('pets'))}</span>;

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'

This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRowIds hook.

const App = ({store}) => (
  <Provider storesById={{petStore: store}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useRowIds('pets', 'petStore'))}</span>
);

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
useRowIdsListener

The useRowIdsListener hook registers a listener function with a Store that will be called whenever the Row Ids in a Table change.

useRowIdsListener(
  tableId: IdOrNull,
  listener: RowIdsListener,
  listenerDeps?: DependencyList,
  mutator?: boolean,
  storeOrStoreId?: StoreOrStoreId,
): void
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

listenerRowIdsListener

The function that will be called whenever the Row Ids in the Table change.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

storeOrStoreId?StoreOrStoreId

The Store to register the listener with: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRowIds hook).

You can either listen to a single Table (by specifying its Id as the method's first parameter) or changes to any Table (by providing null).

Unlike the addRowIdsListener method, which returns a listener Id and requires you to remove it manually, the useRowIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.

Example

This example uses the useRowIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useRowIdsListener('pets', () => console.log('Row Ids changed'));
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(store.getListenerStats().rowIds);
// -> 1

store.setRow('pets', 'felix', {color: 'black'});
// -> 'Row Ids changed'

ReactDOM.unmountComponentAtNode(app);
console.log(store.getListenerStats().rowIds);
// -> 0
useAddRowCallback

The useAddRowCallback hook returns a parameterized callback that can be used to create a new Row in a Store.

useAddRowCallback<Parameter>(
  tableId: string,
  getRow: (parameter: Parameter, store: Store) => Row,
  getRowDeps?: DependencyList,
  storeOrStoreId?: StoreOrStoreId,
  then?: (rowId: undefined | string, store: Store, row: Row) => void,
  thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
TypeDescription
tableIdstring

The Id of the Table in the Store.

getRow(parameter: Parameter, store: Store) => Row

A function which returns the Row object that will be used to update the Store, based on the parameter the callback will receive (and which is most likely a DOM event).

getRowDeps?DependencyList

An optional array of dependencies for the getRow function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(rowId: undefined | string, store: Store, row: Row) => void

A function which is called after the mutation, with the new Row Id, a reference to the Store, and the Row used in the update.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsParameterizedCallback<Parameter>

A parameterized callback for subsequent use.

This hook is useful, for example, when creating an event handler that will mutate the data in Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.

The second parameter is a function which will produce the Row object that will then be used to update the Store in the callback.

If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional third parameter, just as you would for any React hook with dependencies.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.

The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useAddRowCallback hook to create an event handler which updates the Store when the span element is clicked.

const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
  const handleClick = useAddRowCallback(
    'pets',
    (e) => ({species: 'frog', bubbles: e.bubbles}),
    [],
    store,
    (rowId, store, row) => console.log(`Added row: ${rowId}`),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useTable('pets', store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"nemo":{"species":"fish"}}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Added row: 0'

console.log(span.innerHTML);
// -> '{"0":{"species":"frog","bubbles":true},"nemo":{"species":"fish"}}'
useDelRowCallback

The useDelRowCallback hook returns a callback that can be used to remove a single Row from a Table.

useDelRowCallback(
  tableId: string,
  rowId: string,
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store) => void,
  thenDeps?: DependencyList,
): Callback
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store) => void

A function which is called after the deletion, with a reference to the Store.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsCallback

A callback for subsequent use.

This hook is useful, for example, when creating an event handler that will delete data in a Store.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.

The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useDelRowCallback hook to create an event handler which deletes from the Store when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
  const handleClick = useDelRowCallback('pets', 'nemo', store, () =>
    console.log('Deleted'),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useTables(store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'

console.log(span.innerHTML);
// -> '{}'
useRow

The useRow hook returns an object containing the entire data of a single Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.

useRow(
  tableId: string,
  rowId: string,
  storeOrStoreId?: StoreOrStoreId,
): Row
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

storeOrStoreId?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsRow

An object containing the entire data of the Row.

A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useRow hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Row will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Store outside the application, which is used in the useRow hook by reference. A change to the data in the Store re-renders the component.

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
  <span>{JSON.stringify(useRow('pets', 'fido', store))}</span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'

store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"color":"walnut"}</span>'

This example creates a Provider context into which a default Store is provided. A component within it then uses the useRow hook.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{JSON.stringify(useRow('pets', 'fido'))}</span>;

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'

This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useRow hook.

const App = ({store}) => (
  <Provider storesById={{petStore: store}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useRow('pets', 'fido', 'petStore'))}</span>
);

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
useRowListener

The useRowListener hook registers a listener function with a Store that will be called whenever data in a Row changes.

useRowListener(
  tableId: IdOrNull,
  rowId: IdOrNull,
  listener: RowListener,
  listenerDeps?: DependencyList,
  mutator?: boolean,
  storeOrStoreId?: StoreOrStoreId,
): void
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

rowIdIdOrNull

The Id of the Row to listen to, or null as a wildcard.

listenerRowListener

The function that will be called whenever data in the Row changes.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

storeOrStoreId?StoreOrStoreId

The Store to register the listener with: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useRow hook).

You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).

Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.

Unlike the addRowListener method, which returns a listener Id and requires you to remove it manually, the useRowListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.

Example

This example uses the useRowListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useRowListener('pets', 'fido', () => console.log('Row changed'));
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(store.getListenerStats().row);
// -> 1

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Row changed'

ReactDOM.unmountComponentAtNode(app);
console.log(store.getListenerStats().row);
// -> 0
useSetPartialRowCallback

The useSetPartialRowCallback hook returns a parameterized callback that can be used to sets partial data of a single Row in the Store, leaving other Cell values unaffected.

useSetPartialRowCallback<Parameter>(
  tableId: string,
  rowId: string,
  getPartialRow: (parameter: Parameter, store: Store) => Row,
  getPartialRowDeps?: DependencyList,
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store, partialRow: Row) => void,
  thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table to set.

getPartialRow(parameter: Parameter, store: Store) => Row

A function which returns the partial Row object that will be used to update the Store, based on the parameter the callback will receive (and which is most likely a DOM event).

getPartialRowDeps?DependencyList

An optional array of dependencies for the getRow function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store, partialRow: Row) => void

A function which is called after the mutation, with a reference to the Store and the Row used in the update.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsParameterizedCallback<Parameter>

A parameterized callback for subsequent use.

This hook is useful, for example, when creating an event handler that will mutate the data in Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.

The third parameter is a function which will produce the partial Row object that will then be used to update the Store in the callback.

If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.

The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useSetPartialRowCallback hook to create an event handler which updates the Store when the span element is clicked.

const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
  const handleClick = useSetPartialRowCallback(
    'pets',
    'nemo',
    (e) => ({bubbles: e.bubbles}),
    [],
    store,
    (store, partialRow) =>
      console.log(`Updated: ${JSON.stringify(partialRow)}`),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useRow('pets', 'nemo', store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"bubbles":true}'

console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
useSetRowCallback

The useSetRowCallback hook returns a parameterized callback that can be used to set the entire data of a single Row in a Store.

useSetRowCallback<Parameter>(
  tableId: string,
  rowId: string,
  getRow: (parameter: Parameter, store: Store) => Row,
  getRowDeps?: DependencyList,
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store, row: Row) => void,
  thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table to set.

getRow(parameter: Parameter, store: Store) => Row

A function which returns the Row object that will be used to update the Store, based on the parameter the callback will receive (and which is most likely a DOM event).

getRowDeps?DependencyList

An optional array of dependencies for the getRow function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store, row: Row) => void

A function which is called after the mutation, with a reference to the Store and the Row used in the update.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsParameterizedCallback<Parameter>

A parameterized callback for subsequent use.

This hook is useful, for example, when creating an event handler that will mutate the data in Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.

The third parameter is a function which will produce the Row object that will then be used to update the Store in the callback.

If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.

The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useSetRowCallback hook to create an event handler which updates the Store when the span element is clicked.

const store = createStore().setRow('pets', 'nemo', {species: 'fish'});
const App = () => {
  const handleClick = useSetRowCallback(
    'pets',
    'nemo',
    (e) => ({species: 'fish', bubbles: e.bubbles}),
    [],
    store,
    (store, row) => console.log(`Updated: ${JSON.stringify(row)}`),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useRow('pets', 'nemo', store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: {"species":"fish","bubbles":true}'

console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'
useCellIds

The useCellIds hook returns the Ids of every Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.

useCellIds(
  tableId: string,
  rowId: string,
  storeOrStoreId?: StoreOrStoreId,
): Ids
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

storeOrStoreId?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsIds

An array of the Ids of every Cell in the Row.

A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useCellIds hook lets you indicate which Store to get data for: omit the optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Cell Ids will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Store outside the application, which is used in the useCellIds hook by reference. A change to the data in the Store re-renders the component.

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
  <span>{JSON.stringify(useCellIds('pets', 'fido', store))}</span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'

store.setCell('pets', 'fido', 'species', 'dog');
console.log(app.innerHTML);
// -> '<span>["color","species"]</span>'

This example creates a Provider context into which a default Store is provided. A component within it then uses the useCellIds hook.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useCellIds('pets', 'fido'))}</span>
);

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'

This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useCellIds hook.

const App = ({store}) => (
  <Provider storesById={{petStore: store}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{JSON.stringify(useCellIds('pets', 'fido', 'petStore'))}</span>
);

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>["color"]</span>'
useCellIdsListener

The useCellIdsListener hook registers a listener function with a Store that will be called whenever the Cell Ids in a Row change.

useCellIdsListener(
  tableId: IdOrNull,
  rowId: IdOrNull,
  listener: CellIdsListener,
  listenerDeps?: DependencyList,
  mutator?: boolean,
  storeOrStoreId?: StoreOrStoreId,
): void
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

rowIdIdOrNull

The Id of the Row to listen to, or null as a wildcard.

listenerCellIdsListener

The function that will be called whenever the Cell Ids in the Row change.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

storeOrStoreId?StoreOrStoreId

The Store to register the listener with: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCellIds hook).

You can either listen to a single Row (by specifying the Table Id and Row Id as the method's first two parameters) or changes to any Row (by providing null wildcards).

Both, either, or neither of the tableId and rowId parameters can be wildcarded with null. You can listen to a specific Row in a specific Table, any Row in a specific Table, a specific Row in any Table, or any Row in any Table.

Unlike the addCellIdsListener method, which returns a listener Id and requires you to remove it manually, the useCellIdsListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.

Example

This example uses the useCellIdsListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useCellIdsListener('pets', 'fido', () =>
    console.log('Cell Ids changed'),
  );
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(store.getListenerStats().cellIds);
// -> 1

store.setCell('pets', 'fido', 'species', 'dog');
// -> 'Cell Ids changed'

ReactDOM.unmountComponentAtNode(app);
console.log(store.getListenerStats().cellIds);
// -> 0
useCell

The useCell hook returns an object containing the value of a single Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.

useCell(
  tableId: string,
  rowId: string,
  cellId: string,
  storeOrStoreId?: StoreOrStoreId,
): Cell | undefined
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

cellIdstring

The Id of the Cell in the Row.

storeOrStoreId?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsCell | undefined

The value of the Cell.

A Provider component is used to wrap part of an application in a context, and it can contain a default Store or a set of Store objects named by Id. The useCell hook lets you indicate which Store to get data for: omit the final optional final parameter for the default context Store, provide an Id for a named context Store, or provide a Store explicitly by reference.

When first rendered, this hook will create a listener so that changes to the Cell will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.

Examples

This example creates a Store outside the application, which is used in the useCell hook by reference. A change to the data in the Store re-renders the component.

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => <span>{useCell('pets', 'fido', 'color', store)}</span>;

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>brown</span>'

store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'

This example creates a Provider context into which a default Store is provided. A component within it then uses the useCell hook.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => <span>{useCell('pets', 'fido', 'color')}</span>;

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>brown</span>'

This example creates a Provider context into which a Store is provided, named by Id. A component within it then uses the useCell hook.

const App = ({store}) => (
  <Provider storesById={{petStore: store}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>{useCell('pets', 'fido', 'color', 'petStore')}</span>
);

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>brown</span>'
useCellListener

The useCellListener hook registers a listener function with a Store that will be called whenever data in a Cell changes.

useCellListener(
  tableId: IdOrNull,
  rowId: IdOrNull,
  cellId: IdOrNull,
  listener: CellListener,
  listenerDeps?: DependencyList,
  mutator?: boolean,
  storeOrStoreId?: StoreOrStoreId,
): void
TypeDescription
tableIdIdOrNull

The Id of the Table to listen to, or null as a wildcard.

rowIdIdOrNull

The Id of the Row to listen to, or null as a wildcard.

cellIdIdOrNull

The Id of the Cell to listen to, or null as a wildcard.

listenerCellListener

The function that will be called whenever data in the Cell changes.

listenerDeps?DependencyList

An optional array of dependencies for the listener function, which, if any change, result in the re-registration of the listener. This parameter defaults to an empty array.

mutator?boolean

An optional boolean that indicates that the listener mutates Store data.

storeOrStoreId?StoreOrStoreId

The Store to register the listener with: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

returnsvoid

This has no return value.

This hook is useful for situations where a component needs to register its own specific listener to do more than simply tracking the value (which is more easily done with the useCell hook).

You can either listen to a single Cell (by specifying the Table Id, Row Id, and Cell Id as the method's first three parameters) or changes to any Cell (by providing null wildcards).

All, some, or none of the tableId, rowId, and cellId parameters can be wildcarded with null. You can listen to a specific Cell in a specific Row in a specific Table, any Cell in any Row in any Table, for example - or every other combination of wildcards.

Unlike the addCellListener method, which returns a listener Id and requires you to remove it manually, the useCellListener hook manages this lifecycle for you: when the listener changes (per its listenerDeps dependencies) or the component unmounts, the listener on the underlying Store will be deleted.

Example

This example uses the useCellListener hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Store.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => {
  useCellListener('pets', 'fido', 'color', () =>
    console.log('Cell changed'),
  );
  return <span>App</span>;
};

const store = createStore().setTables({pets: {fido: {color: 'brown'}}});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(store.getListenerStats().cell);
// -> 1

store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Cell changed'

ReactDOM.unmountComponentAtNode(app);
console.log(store.getListenerStats().cell);
// -> 0
useDelCellCallback

The useDelCellCallback hook returns a callback that can be used to remove a single Cell from a Row.

useDelCellCallback(
  tableId: string,
  rowId: string,
  cellId: string,
  forceDel?: boolean,
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store) => void,
  thenDeps?: DependencyList,
): Callback
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

cellIdstring

The Id of the Cell in the Row.

forceDel?boolean

An optional flag to indicate that the whole Row should be deleted, even if a Schema provides a default value for this Cell. Defaults to false.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store) => void

A function which is called after the deletion, with a reference to the Store.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsCallback

A callback for subsequent use.

This hook is useful, for example, when creating an event handler that will delete data in a Store.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the deletion to your application's undo stack.

The Store to which the callback will make the deletion (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Example

This example uses the useDelCellCallback hook to create an event handler which deletes from the Store when the span element is clicked.

const store = createStore().setTables({pets: {nemo: {species: 'fish'}}});
const App = () => {
  const handleClick = useDelCellCallback(
    'pets',
    'nemo',
    'species',
    false,
    store,
    () => console.log('Deleted'),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useTables(store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"pets":{"nemo":{"species":"fish"}}}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Deleted'

console.log(span.innerHTML);
// -> '{}'
useSetCellCallback

The useSetCellCallback hook returns a parameterized callback that can be used to set the value of a single Cell in a Store.

useSetCellCallback<Parameter>(
  tableId: string,
  rowId: string,
  cellId: string,
  getCell: (parameter: Parameter, store: Store) => Cell | MapCell,
  getCellDeps?: DependencyList,
  storeOrStoreId?: StoreOrStoreId,
  then?: (store: Store, cell: Cell | MapCell) => void,
  thenDeps?: DependencyList,
): ParameterizedCallback<Parameter>
TypeDescription
tableIdstring

The Id of the Table in the Store.

rowIdstring

The Id of the Row in the Table.

cellIdstring

The Id of the Cell in the Row to set.

getCell(parameter: Parameter, store: Store) => Cell | MapCell

A function which returns the Cell value that will be used to update the Store, or a MapCell function to update it, based on the parameter the callback will receive (and which is most likely a DOM event).

getCellDeps?DependencyList

An optional array of dependencies for the getCell function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

storeOrStoreId?StoreOrStoreId

The Store to be updated: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

then?(store: Store, cell: Cell | MapCell) => void

A function which is called after the mutation, with a reference to the Store and the Cell value (or MapCell function) used in the update.

thenDeps?DependencyList

An optional array of dependencies for the then function, which, if any change, result in the regeneration of the callback. This parameter defaults to an empty array.

returnsParameterizedCallback<Parameter>

A parameterized callback for subsequent use.

This hook is useful, for example, when creating an event handler that will mutate the data in Store. In this case, the parameter will likely be the event, so that you can use data from it as part of the mutation.

The fourth parameter is a function which will produce the Cell object that will then be used to update the Store in the callback.

If that function has any other dependencies, the changing of which should also cause the callback to be recreated, you can provide them in an array in the optional fourth parameter, just as you would for any React hook with dependencies.

For convenience, you can optionally provide a then function (with its own set of dependencies) which will be called just after the Store has been updated. This is a useful place to call the addCheckpoint method, for example, if you wish to add the mutation to your application's undo stack.

The Store to which the callback will make the mutation (indicated by the hook's storeOrStoreId parameter) is always automatically used as a hook dependency for the callback.

Examples

This example uses the useSetCellCallback hook to create an event handler which updates the Store with a Cell value when the span element is clicked.

const store = createStore().setCell('pets', 'nemo', 'species', 'fish');
const App = () => {
  const handleClick = useSetCellCallback(
    'pets',
    'nemo',
    'bubbles',
    (e) => e.bubbles,
    [],
    store,
    (store, cell) => console.log(`Updated: ${cell}`),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useRow('pets', 'nemo', store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"species":"fish"}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated: true'

console.log(span.innerHTML);
// -> '{"species":"fish","bubbles":true}'

This example uses the useSetCellCallback hook to create an event handler which updates the Store via a MapCell function when the span element is clicked.

const store = createStore().setCell('pets', 'nemo', 'visits', 1);
const App = () => {
  const handleClick = useSetCellCallback(
    'pets',
    'nemo',
    'visits',
    (e) => (visits) => visits + (e.bubbles ? 1 : 0),
    [],
    store,
    (store, cell) => console.log(`Updated with MapCell function`),
  );
  return (
    <span id="span" onClick={handleClick}>
      {JSON.stringify(useRow('pets', 'nemo', store))}
    </span>
  );
};

const app = document.createElement('div');
ReactDOM.render(<App />, app);
const span = app.querySelector('span');
console.log(span.innerHTML);
// -> '{"visits":1}'

// User clicks the <span> element:
// -> span MouseEvent('click', {bubbles: true})
// -> 'Updated with MapCell function'

console.log(span.innerHTML);
// -> '{"visits":2}'

Checkpoints components

This is the collection of checkpoints components within the ui-react module. There are 4 checkpoints components in total.

BackwardCheckpointsView

The BackwardCheckpointsView component renders a list of previous checkpoints that the underlying Store can go back to.

BackwardCheckpointsView(props: BackwardCheckpointsProps): ComponentReturnType
TypeDescription
propsBackwardCheckpointsProps

The props for this component.

returnsComponentReturnType

A rendering of the previous checkpoints, if present.

The component's props identify which previous checkpoints to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).

This component renders a list by iterating over each checkpoints. By default these are in turn rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.

This component uses the useCheckpointIds hook under the covers, which means that any changes to the checkpoint Ids in the Checkpoints object will cause a re-render.

Examples

This example creates an Checkpoints object outside the application, which is used in the BackwardCheckpointsView component by reference to render a list of previous checkpoints.

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
  <div>
    <BackwardCheckpointsView checkpoints={checkpoints} separator="/" />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div></div>'

checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
console.log(app.innerHTML);
// -> '<div>initial</div>'

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>initial/identified</div>'

This example creates a Provider context into which a default Checkpoints object is provided. The BackwardCheckpointsView component within it then renders the list of previous checkpoints (with Ids for readability).

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <BackwardCheckpointsView debugIds={true} />
  </div>
);

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);

checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<div>0:{initial}1:{identified}</div>'

This example creates a Provider context into which a default Checkpoints object is provided. The BackwardCheckpointsView component within it then renders the list of previous checkpoints with a custom Row component and a custom props callback.

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <BackwardCheckpointsView
      checkpointComponent={FormattedCheckpointView}
      getCheckpointComponentProps={(checkpointId) => ({
        bold: checkpointId == '0',
      })}
    />
  </div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
  <span>
    {bold ? <b>{checkpointId}</b> : checkpointId}
    {': '}
    <CheckpointView
      checkpoints={checkpoints}
      checkpointId={checkpointId}
    />
  </span>
);

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);

checkpoints.setCheckpoint('0', 'initial');
store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>0</b>: initial</span><span>1: identified</span></div>'
CheckpointView

The CheckpointView component simply renders the label of a checkpoint.

CheckpointView(props: CheckpointProps): ComponentReturnType
TypeDescription
propsCheckpointProps

The props for this component.

returnsComponentReturnType

A rendering of the checkpoint: its label if present, or Id.

The component's props identify which checkpoint to render based on Checkpoint Id and Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).

The primary purpose of this component is to render multiple checkpoints in a BackwardCheckpointsView component or ForwardCheckpointsView component.

This component uses the useCheckpoint hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.

Example

This example creates an Checkpoints object outside the application, which is used in the CheckpointView component by reference to render a checkpoint with a label (with its Id for readability).

const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const checkpoints = createCheckpoints(store);
const App = () => (
  <div>
    <CheckpointView
      checkpointId="1"
      checkpoints={checkpoints}
      debugIds={true}
    />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>1:{}</div>'

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>1:{sale}</div>'

checkpoints.setCheckpoint('1', 'sold');
console.log(app.innerHTML);
// -> '<div>1:{sold}</div>'
CurrentCheckpointView

The CurrentCheckpointView component renders the current checkpoint that the underlying Store is currently on.

CurrentCheckpointView(props: CurrentCheckpointProps): ComponentReturnType
TypeDescription
propsCurrentCheckpointProps

The props for this component.

returnsComponentReturnType

A rendering of the current checkpoint, if present.

The component's props identify which current checkpoint to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).

By default the current checkpoint is rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.

This component uses the useCheckpointIds hook under the covers, which means that any changes to the current checkpoint Id in the Checkpoints object will cause a re-render.

Examples

This example creates an Checkpoints object outside the application, which is used in the CurrentCheckpointView component by reference to render the current checkpoints.

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
  <div>
    <CurrentCheckpointView checkpoints={checkpoints} />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div></div>'

store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
console.log(app.innerHTML);
// -> '<div>identified</div>'

store.setCell('pets', 'fido', 'sold', true);
console.log(app.innerHTML);
// -> '<div></div>'

checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div>sale</div>'

This example creates a Provider context into which a default Checkpoints object is provided. The CurrentCheckpointView component within it then renders current checkpoint (with its Id for readability).

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <CurrentCheckpointView debugIds={true} />
  </div>
);

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);

store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<div>1:{identified}</div>'

This example creates a Provider context into which a default Checkpoints object is provided. The CurrentCheckpointView component within it then renders the list of future checkpoints with a custom Row component and a custom props callback.

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <CurrentCheckpointView
      checkpointComponent={FormattedCheckpointView}
      getCheckpointComponentProps={(checkpointId) => ({
        bold: checkpointId == '1',
      })}
    />
  </div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
  <span>
    {bold ? <b>{checkpointId}</b> : checkpointId}
    {': '}
    <CheckpointView
      checkpoints={checkpoints}
      checkpointId={checkpointId}
    />
  </span>
);

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);

store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>1</b>: identified</span></div>'

store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
console.log(app.innerHTML);
// -> '<div><span>2: sale</span></div>'
ForwardCheckpointsView

The ForwardCheckpointsView component renders a list of future checkpoints that the underlying Store can go forwards to.

ForwardCheckpointsView(props: ForwardCheckpointsProps): ComponentReturnType
TypeDescription
propsForwardCheckpointsProps

The props for this component.

returnsComponentReturnType

A rendering of the future checkpoints, if present.

The component's props identify which future checkpoints to render based on the Checkpoints object (which is either the default context Checkpoints object, a named context Checkpoints object, or an explicit reference).

This component renders a list by iterating over each checkpoints. By default these are in turn rendered with the CheckpointView component, but you can override this behavior by providing a checkpointComponent prop, a custom component of your own that will render a checkpoint based on CheckpointProps. You can also pass additional props to your custom component with the getCheckpointComponentProps callback prop.

This component uses the useCheckpointIds hook under the covers, which means that any changes to the checkpoint Ids in the Checkpoints object will cause a re-render.

Examples

This example creates an Checkpoints object outside the application, which is used in the ForwardCheckpointsView component by reference to render a list of future checkpoints.

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);
const App = () => (
  <div>
    <ForwardCheckpointsView checkpoints={checkpoints} separator="/" />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div></div>'

store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goBackward();
console.log(app.innerHTML);
// -> '<div>sale</div>'

checkpoints.goBackward();
console.log(app.innerHTML);
// -> '<div>identified/sale</div>'

This example creates a Provider context into which a default Checkpoints object is provided. The ForwardCheckpointsView component within it then renders the list of future checkpoints (with Ids for readability).

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <ForwardCheckpointsView debugIds={true} />
  </div>
);

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);

store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goTo('0');

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<div>1:{identified}2:{sale}</div>'

This example creates a Provider context into which a default Checkpoints object is provided. The ForwardCheckpointsView component within it then renders the list of future checkpoints with a custom Row component and a custom props callback.

const App = ({checkpoints}) => (
  <Provider checkpoints={checkpoints}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <ForwardCheckpointsView
      checkpointComponent={FormattedCheckpointView}
      getCheckpointComponentProps={(checkpointId) => ({
        bold: checkpointId == '1',
      })}
    />
  </div>
);
const FormattedCheckpointView = ({checkpoints, checkpointId, bold}) => (
  <span>
    {bold ? <b>{checkpointId}</b> : checkpointId}
    {': '}
    <CheckpointView
      checkpoints={checkpoints}
      checkpointId={checkpointId}
    />
  </span>
);

const store = createStore().setTable('pets', {fido: {color: 'brown'}});
const checkpoints = createCheckpoints(store);

store.setCell('pets', 'fido', 'species', 'dog');
checkpoints.addCheckpoint('identified');
store.setCell('pets', 'fido', 'sold', true);
checkpoints.addCheckpoint('sale');
checkpoints.goTo('0');

const app = document.createElement('div');
ReactDOM.render(<App checkpoints={checkpoints} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>1</b>: identified</span><span>2: sale</span></div>'

Context components

This is the collection of context components within the ui-react module. There is only one function, Provider.

Provider

The Provider component is used to wrap part of an application in a context that provides default objects to be used by hooks and components within.

Provider(props: ProviderProps & {children: ReactNode}): ComponentReturnType
TypeDescription
propsProviderProps & {children: ReactNode}

The props for this component.

returnsComponentReturnType

A rendering of the child components.

Store, Metrics, Indexes, Relationships, and Checkpoints objects can be passed into the context of an application and used throughout. One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id-keyed map to the ___ById props.

Provider contexts can be nested and the objects passed in will be merged. For example, if an outer context contains a default Metrics object and an inner context contains only a default Store, both the Metrics objects and the Store will be visible within the inner context. If the outer context contains a Store named by Id and the inner context contains a Store named by a different Id, both will be visible within the inner context.

Examples

This example creates a Provider context into which a Store and a Metrics object are provided, one by default, and one named by Id. Components within it then render content from both, without the need to have them passed as props.

const App = ({store, metrics}) => (
  <Provider store={store} metricsById={{petStore: metrics}}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>
    <CellView tableId="species" rowId="dog" cellId="price" />,
    <CellView tableId="species" rowId="cat" cellId="price" />,
    {useMetric('highestPrice', 'petStore')}
  </span>
);

const store = createStore();
store.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

const app = document.createElement('div');
ReactDOM.render(<App store={store} metrics={metrics} />, app);
console.log(app.innerHTML);
// -> '<span>5,4,5</span>'

This example creates nested Provider contexts into which Store and Metrics objects are provided, showing how visibility is merged.

const App = ({petStore, metrics}) => (
  <Provider storesById={{pet: petStore}} metrics={metrics}>
    <OuterPane />
  </Provider>
);
const OuterPane = () => {
  const planetStore = useCreateStore(() =>
    createStore().setTables({planets: {mars: {moons: 2}}}),
  );
  return (
    <Provider storesById={{planet: planetStore}}>
      <InnerPane />
    </Provider>
  );
};
const InnerPane = () => (
  <span>
    <CellView tableId="species" rowId="dog" cellId="price" store="pet" />,
    {useMetric('highestPrice')},
    <CellView
      tableId="planets"
      rowId="mars"
      cellId="moons"
      store="planet"
    />
  </span>
);

const petStore = createStore();
petStore.setTable('species', {dog: {price: 5}, cat: {price: 4}});
const metrics = createMetrics(petStore);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

const app = document.createElement('div');
ReactDOM.render(<App petStore={petStore} metrics={metrics} />, app);
console.log(app.innerHTML);
// -> '<span>5,5,2</span>'

Indexes components

This is the collection of indexes components within the ui-react module. There are only two indexes components, IndexView and SliceView.

IndexView

The IndexView component renders the contents of a Index, and registers a listener so that any changes to that result will cause a re-render.

IndexView(props: IndexProps): ComponentReturnType
TypeDescription
propsIndexProps

The props for this component.

returnsComponentReturnType

A rendering of the Index, or nothing, if not present.

The component's props identify which Index to render based on Index Id, and Indexes object (which is either the default context Indexes object, a named context Indexes object, or an explicit reference).

This component renders a Index by iterating over its Slice objects. By default these are in turn rendered with the SliceView component, but you can override this behavior by providing a sliceComponent prop, a custom component of your own that will render a Slice based on SliceProps. You can also pass additional props to your custom component with the getSliceComponentProps callback prop.

This component uses the useSliceIds hook under the covers, which means that any changes to the structure of the Index will cause a re-render.

Examples

This example creates an Indexes object outside the application, which is used in the IndexView component by reference. A change to the Slice Ids re-renders the component.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
  <div>
    <IndexView indexId="bySpecies" indexes={indexes} separator="/" />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>dog/cat</div>'

store.setRow('pets', 'lowly', {species: 'worm'});
console.log(app.innerHTML);
// -> '<div>dog/cat/worm</div>'

This example creates a Provider context into which a default Indexes object is provided. The IndexView component within it then renders the Index (with Ids for readability).

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <IndexView indexId="bySpecies" debugIds={true} />
  </div>
);

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<div>bySpecies:{dog:{fido:{species:{dog}}cujo:{species:{dog}}}}</div>'

This example creates a Provider context into which a default Indexes object is provided. The IndexView component within it then renders the Index with a custom Slice component and a custom props callback.

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <IndexView
      indexId="bySpecies"
      sliceComponent={FormattedSliceView}
      getSliceComponentProps={(sliceId) => ({bold: sliceId == 'dog'})}
    />
  </div>
);
const FormattedSliceView = ({indexId, sliceId, bold}) => (
  <span>
    {bold ? <b>{sliceId}</b> : sliceId}
    {': '}
    <SliceView indexId={indexId} sliceId={sliceId} separator="/" />
  </span>
);

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>dog</b>: dog/dog</span><span>cat: cat</span></div>'
SliceView

The SliceView component renders the contents of a Slice, and registers a listener so that any changes to that result will cause a re-render.

SliceView(props: SliceProps): ComponentReturnType
TypeDescription
propsSliceProps

The props for this component.

returnsComponentReturnType

A rendering of the Slice, or nothing, if not present.

The component's props identify which Slice to render based on Index Id, Slice Id, and Indexes object (which is either the default context Indexes object, a named context Indexes object, or an explicit reference).

This component renders a Slice by iterating over its Row objects. By default these are in turn rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render a Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.

This component uses the useSliceRowIds hook under the covers, which means that any changes to the structure of the Slice will cause a re-render.

Examples

This example creates an Indexes object outside the application, which is used in the SliceView component by reference. A change to the Row Ids re-renders the component.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');
const App = () => (
  <div>
    <SliceView
      indexId="bySpecies"
      sliceId="dog"
      indexes={indexes}
      separator="/"
    />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>dog</div>'

store.setRow('pets', 'cujo', {species: 'dog'});
console.log(app.innerHTML);
// -> '<div>dog/dog</div>'

This example creates a Provider context into which a default Indexes object is provided. The SliceView component within it then renders the Slice (with Ids for readability).

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <SliceView indexId="bySpecies" sliceId="dog" debugIds={true} />
  </div>
);

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<div>dog:{fido:{species:{dog}}cujo:{species:{dog}}}</div>'

This example creates a Provider context into which a default Indexes object is provided. The SliceView component within it then renders the Slice with a custom Row component and a custom props callback.

const App = ({indexes}) => (
  <Provider indexes={indexes}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <SliceView
      indexId="bySpecies"
      sliceId="dog"
      rowComponent={FormattedRowView}
      getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
    />
  </div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
  <span>
    {bold ? <b>{rowId}</b> : rowId}
    {': '}
    <RowView store={store} tableId={tableId} rowId={rowId} separator="/" />
  </span>
);

const store = createStore().setTable('pets', {
  fido: {species: 'dog', color: 'brown'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});
const indexes = createIndexes(store);
indexes.setIndexDefinition('bySpecies', 'pets', 'species');

const app = document.createElement('div');
ReactDOM.render(<App indexes={indexes} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog/brown</span><span>cujo: dog</span></div>'

Metrics components

This is the collection of metrics components within the ui-react module. There is only one function, MetricView.

MetricView

The MetricView component renders the current value of a Metric, and registers a listener so that any changes to that result will cause a re-render.

MetricView(props: MetricProps): ComponentReturnType
TypeDescription
propsMetricProps

The props for this component.

returnsComponentReturnType

A rendering of the Metric, or nothing, if not present.

The component's props can identify which Metrics object to get data for: omit the optional final parameter for the default context Metrics object, provide an Id for a named context Metrics object, or by explicit reference.

This component uses the useMetric hook under the covers, which means that any changes to the Metric will cause a re-render.

Examples

This example creates a Metrics object outside the application, which is used in the MetricView component hook by reference. A change to the Metric re-renders the component.

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');
const App = () => (
  <div>
    <MetricView metricId="highestPrice" metrics={metrics} />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>5</div>'

store.setCell('species', 'horse', 'price', 20);
console.log(app.innerHTML);
// -> '<div>20</div>'

This example creates a Provider context into which a default Metrics object is provided. The MetricView component within it then renders the Metric (with its Id for readability).

const App = ({metrics}) => (
  <Provider metrics={metrics}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <MetricView metricId="highestPrice" debugIds={true} />
  </div>
);

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

const app = document.createElement('div');
ReactDOM.render(<App metrics={metrics} />, app);
console.log(app.innerHTML);
// -> '<div>highestPrice:{5}</div>'

This example creates a Provider context into which a default Metrics object is provided. The MetricView component within it then attempts to render a non-existent Metric.

const App = ({metrics}) => (
  <Provider metrics={metrics}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <MetricView metricId="lowestPrice" />
  </div>
);

const store = createStore().setTable('species', {
  dog: {price: 5},
  cat: {price: 4},
  worm: {price: 1},
});
const metrics = createMetrics(store);
metrics.setMetricDefinition('highestPrice', 'species', 'max', 'price');

const app = document.createElement('div');
ReactDOM.render(<App metrics={metrics} />, app);
console.log(app.innerHTML);
// -> '<div></div>'

Relationships components

This is the collection of relationships components within the ui-react module. There are only three relationships components, LinkedRowsView, LocalRowsView, and RemoteRowView.

LinkedRowsView

The LinkedRowsView component renders the local Row objects for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.

LinkedRowsView(props: LinkedRowsProps): ComponentReturnType
TypeDescription
propsLinkedRowsProps

The props for this component.

returnsComponentReturnType

A rendering of the local Row objects, or nothing, if not present.

The component's props identify which local Rows to render based on Relationship Id, remote Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).

By default the local Rows are rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.

This component uses the useLocalRowIds hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.

Examples

This example creates an Relationships object outside the application, which is used in the LinkedRowsView component by reference. A change to the Row Ids re-renders the component.

const store = createStore().setTable('pets', {
  fido: {next: 'felix'},
  felix: {next: 'cujo'},
  cujo: {species: 'dog'},
});
const relationships = createRelationships(store).setRelationshipDefinition(
  'petSequence',
  'pets',
  'pets',
  'next',
);
const App = () => (
  <div>
    <LinkedRowsView
      relationshipId="petSequence"
      firstRowId="fido"
      relationships={relationships}
      separator="/"
    />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>felix/cujo/dog</div>'

store.setRow('pets', 'toto', {species: 'dog'});
store.setRow('pets', 'cujo', {next: 'toto'});
console.log(app.innerHTML);
// -> '<div>felix/cujo/toto/dog</div>'

This example creates a Provider context into which a default Relationships object is provided. The LinkedRowsView component within it then renders the local Row objects (with Ids for readability).

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <LinkedRowsView
      relationshipId="petSequence"
      firstRowId="fido"
      debugIds={true}
    />
  </div>
);

const relationships = createRelationships(
  createStore().setTable('pets', {
    fido: {next: 'felix'},
    felix: {species: 'cat'},
  }),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<div>fido:{fido:{next:{felix}}felix:{species:{cat}}}</div>'

This example creates a Provider context into which a default Relationships object is provided. The LinkedRowsView component within it then renders the local Row objects with a custom Row component and a custom props callback.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <LinkedRowsView
      relationshipId="petSequence"
      firstRowId="fido"
      rowComponent={FormattedRowView}
      getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
    />
  </div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
  <span>
    {bold ? <b>{rowId}</b> : rowId}
    {': '}
    <RowView store={store} tableId={tableId} rowId={rowId} />
  </span>
);

const relationships = createRelationships(
  createStore().setTable('pets', {
    fido: {next: 'felix'},
    felix: {species: 'cat'},
  }),
).setRelationshipDefinition('petSequence', 'pets', 'pets', 'next');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: felix</span><span>felix: cat</span></div>'
LocalRowsView

The LocalRowsView component renders the local Row objects for a given remote Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.

LocalRowsView(props: LocalRowsProps): ComponentReturnType
TypeDescription
propsLocalRowsProps

The props for this component.

returnsComponentReturnType

A rendering of the local Row objects, or nothing, if not present.

The component's props identify which local Rows to render based on Relationship Id, remote Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).

By default the local Rows are rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.

This component uses the useLocalRowIds hook under the covers, which means that any changes to the local Row Ids in the Relationship will cause a re-render.

Examples

This example creates an Relationships object outside the application, which is used in the LocalRowsView component by reference. A change to the Row Ids re-renders the component.

const store = createStore()
  .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
  .setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);
const App = () => (
  <div>
    <LocalRowsView
      relationshipId="petSpecies"
      remoteRowId="dog"
      relationships={relationships}
      separator="/"
    />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>dog/dog</div>'

store.setRow('pets', 'toto', {species: 'dog'});
console.log(app.innerHTML);
// -> '<div>dog/dog/dog</div>'

This example creates a Provider context into which a default Relationships object is provided. The LocalRowsView component within it then renders the local Row objects (with Ids for readability).

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <LocalRowsView
      relationshipId="petSpecies"
      remoteRowId="dog"
      debugIds={true}
    />
  </div>
);

const relationships = createRelationships(
  createStore()
    .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
    .setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<div>dog:{fido:{species:{dog}}cujo:{species:{dog}}}</div>'

This example creates a Provider context into which a default Relationships object is provided. The LocalRowsView component within it then renders the local Row objects with a custom Row component and a custom props callback.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <LocalRowsView
      relationshipId="petSpecies"
      remoteRowId="dog"
      rowComponent={FormattedRowView}
      getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
    />
  </div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
  <span>
    {bold ? <b>{rowId}</b> : rowId}
    {': '}
    <RowView store={store} tableId={tableId} rowId={rowId} />
  </span>
);

const relationships = createRelationships(
  createStore()
    .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
    .setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog</span><span>cujo: dog</span></div>'
RemoteRowView

The RemoteRowView component renders the remote Row Id for a given local Row in a Relationship, and registers a listener so that any changes to that result will cause a re-render.

RemoteRowView(props: RemoteRowProps): ComponentReturnType
TypeDescription
propsRemoteRowProps

The props for this component.

returnsComponentReturnType

A rendering of the remote Row, or nothing, if not present.

The component's props identify which remote Row to render based on Relationship Id, local Row Id, and Relationships object (which is either the default context Relationships object, a named context Relationships object, or an explicit reference).

By default the remote Row is rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render the Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.

This component uses the useRemoteRowId hook under the covers, which means that any changes to the remote Row Id in the Relationship will cause a re-render.

Examples

This example creates an Relationships object outside the application, which is used in the RemoteRowView component by reference. A change to the Row Ids re-renders the component.

const store = createStore()
  .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
  .setTable('species', {wolf: {price: 10}, dog: {price: 5}});
const relationships = createRelationships(store).setRelationshipDefinition(
  'petSpecies',
  'pets',
  'species',
  'species',
);
const App = () => (
  <div>
    <RemoteRowView
      relationshipId="petSpecies"
      localRowId="cujo"
      relationships={relationships}
    />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>5</div>'

store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<div>10</div>'

This example creates a Provider context into which a default Relationships object is provided. The RemoteRowView component within it then renders the remote Row (with Ids for readability).

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <RemoteRowView
      relationshipId="petSpecies"
      localRowId="cujo"
      debugIds={true}
    />
  </div>
);

const relationships = createRelationships(
  createStore()
    .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
    .setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<div>cujo:{dog:{price:{5}}}</div>'

This example creates a Provider context into which a default Relationships object is provided. The RemoteRowView component within it then renders the remote Row with a custom Row component and a custom props callback.

const App = ({relationships}) => (
  <Provider relationships={relationships}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <RemoteRowView
      relationshipId="petSpecies"
      localRowId="cujo"
      rowComponent={FormattedRowView}
      getRowComponentProps={(rowId) => ({bold: rowId == 'dog'})}
    />
  </div>
);
const FormattedRowView = ({store, tableId, rowId, bold}) => (
  <span>
    {bold ? <b>{rowId}</b> : rowId}
    {': '}
    <RowView store={store} tableId={tableId} rowId={rowId} />
  </span>
);

const relationships = createRelationships(
  createStore()
    .setTable('pets', {fido: {species: 'dog'}, cujo: {species: 'dog'}})
    .setTable('species', {wolf: {price: 10}, dog: {price: 5}}),
).setRelationshipDefinition('petSpecies', 'pets', 'species', 'species');

const app = document.createElement('div');
ReactDOM.render(<App relationships={relationships} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>dog</b>: 5</span></div>'

Store components

This is the collection of store components within the ui-react module. There are 4 store components in total.

TablesView

The TablesView component renders the contents of a Store, and registers a listener so that any changes to that result will cause a re-render.

TablesView(props: TablesProps): ComponentReturnType
TypeDescription
propsTablesProps

The props for this component.

returnsComponentReturnType

A rendering of the Store, or nothing, if not present.

The component's props can identify which Store to render - either the default context Store, a named context Store, or an explicit reference.

This component renders a Store by iterating over its Table objects. By default these are in turn rendered with the TableView component, but you can override this behavior by providing a tableComponent prop, a custom component of your own that will render a Table based on TableProps. You can also pass additional props to your custom component with the getTableComponentProps callback prop.

This component uses the useTableIds hook under the covers, which means that any changes to the structure of the Store will cause a re-render.

Examples

This example creates a Store outside the application, which is used in the TablesView component by reference. A change to the data in the Store re-renders the component.

const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
const App = () => (
  <div>
    <TablesView store={store} />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>dog</div>'

store.setTable('species', {dog: {price: 5}});
console.log(app.innerHTML);
// -> '<div>dog5</div>'

This example creates a Provider context into which a default Store is provided. The TablesView component within it then renders the Store (with Ids for readability).

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <TablesView tableId="pets" debugIds={true} />
  </div>
);

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}},
  species: {dog: {price: 5}},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<div>pets:{fido:{species:{dog}}}species:{dog:{price:{5}}}</div>'

This example creates a Provider context into which a default Store is provided. The TablesView component within it then renders the Store with a custom Table component and a custom props callback.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <TablesView
      tableComponent={FormattedTableView}
      getTableComponentProps={(tableId) => ({bold: tableId == 'pets'})}
    />
  </div>
);
const FormattedTableView = ({tableId, bold}) => (
  <span>
    {bold ? <b>{tableId}</b> : tableId}
    {': '}
    <TableView tableId={tableId} />
  </span>
);

const store = createStore().setTables({
  pets: {fido: {species: 'dog'}},
  species: {dog: {price: 5}},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>pets</b>: dog</span><span>species: 5</span></div>'
TableView

The TableView component renders the contents of a single Table in a Store, and registers a listener so that any changes to that result will cause a re-render.

TableView(props: TableProps): ComponentReturnType
TypeDescription
propsTableProps

The props for this component.

returnsComponentReturnType

A rendering of the Table, or nothing, if not present.

The component's props identify which Table to render based on Table Id, and Store (which is either the default context Store, a named context Store, or by explicit reference).

This component renders a Table by iterating over its Row objects. By default these are in turn rendered with the RowView component, but you can override this behavior by providing a rowComponent prop, a custom component of your own that will render a Row based on RowProps. You can also pass additional props to your custom component with the getRowComponentProps callback prop.

You can create your own TableView-like component to customize the way that a Table is rendered: see the TablesView component for more details.

This component uses the useRowIds hook under the covers, which means that any changes to the structure of the Table will cause a re-render.

Examples

This example creates a Store outside the application, which is used in the TableView component by reference. A change to the data in the Store re-renders the component.

const store = createStore().setTable('pets', {fido: {species: 'dog'}});
const App = () => (
  <div>
    <TableView tableId="pets" store={store} separator="/" />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>dog</div>'

store.setRow('pets', 'felix', {species: 'cat'});
console.log(app.innerHTML);
// -> '<div>dog/cat</div>'

This example creates a Provider context into which a default Store is provided. The TableView component within it then renders the Table (with Ids for readability).

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <TableView tableId="pets" debugIds={true} />
  </div>
);

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<div>pets:{fido:{species:{dog}}felix:{species:{cat}}}</div>'

This example creates a Provider context into which a default Store is provided. The TableView component within it then renders the Table with a custom Row component and a custom props callback.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <TableView
      tableId="pets"
      rowComponent={FormattedRowView}
      getRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
    />
  </div>
);
const FormattedRowView = ({tableId, rowId, bold}) => (
  <span>
    {bold ? <b>{rowId}</b> : rowId}
    {': '}
    <RowView tableId={tableId} rowId={rowId} />
  </span>
);

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: dog</span><span>felix: cat</span></div>'
RowView

The RowView component renders the contents of a single Row in a given Table, and registers a listener so that any changes to that result will cause a re-render.

RowView(props: RowProps): ComponentReturnType
TypeDescription
propsRowProps

The props for this component.

returnsComponentReturnType

A rendering of the Row, or nothing, if not present.

The component's props identify which Row to render based on Table Id, Row Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).

This component renders a Row by iterating over its Cell values. By default these are in turn rendered with the CellView component, but you can override this behavior by providing a cellComponent prop, a custom component of your own that will render a Cell based on CellProps. You can also pass additional props to your custom component with the getCellComponentProps callback prop.

You can create your own RowView-like component to customize the way that a Row is rendered: see the TableView component for more details.

This component uses the useCellIds hook under the covers, which means that any changes to the structure of the Row will cause a re-render.

Examples

This example creates a Store outside the application, which is used in the RowView component by reference. A change to the data in the Store re-renders the component.

const store = createStore().setRow('pets', 'fido', {species: 'dog'});
const App = () => (
  <div>
    <RowView tableId="pets" rowId="fido" store={store} separator="/" />
  </div>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>dog</div>'

store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<div>dog/walnut</div>'

This example creates a Provider context into which a default Store is provided. The RowView component within it then renders the Row (with Ids for readability).

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <RowView tableId="pets" rowId="fido" debugIds={true} />
  </div>
);

const store = createStore().setRow('pets', 'fido', {
  species: 'dog',
  color: 'walnut',
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<div>fido:{species:{dog}color:{walnut}}</div>'

This example creates a Provider context into which a default Store is provided. The RowView component within it then renders the Row with a custom Cell component and a custom props callback.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <div>
    <RowView
      tableId="pets"
      rowId="fido"
      cellComponent={FormattedCellView}
      getCellComponentProps={(cellId) => ({bold: cellId == 'species'})}
    />
  </div>
);
const FormattedCellView = ({tableId, rowId, cellId, bold}) => (
  <span>
    {bold ? <b>{cellId}</b> : cellId}
    {': '}
    <CellView tableId={tableId} rowId={rowId} cellId={cellId} />
  </span>
);

const store = createStore().setRow('pets', 'fido', {
  species: 'dog',
  color: 'walnut',
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>species</b>: dog</span><span>color: walnut</span></div>'
CellView

The CellView component renders the value of a single Cell in a given Row, in a given Table, and registers a listener so that any changes to that result will cause a re-render.

CellView(props: CellProps): ComponentReturnType
TypeDescription
propsCellProps

The props for this component.

returnsComponentReturnType

A rendering of the Cell, or nothing, if not present.

The component's props identify which Cell to render based on Table Id, Row Id, Cell Id, and Store (which is either the default context Store, a named context Store, or an explicit reference).

A Cell contains a string, number, or boolean, so the value is rendered directly without further decoration. You can create your own CellView-like component to customize the way that a Cell is rendered: see the RowView component for more details.

This component uses the useCell hook under the covers, which means that any changes to the specified Cell will cause a re-render.

Examples

This example creates a Store outside the application, which is used in the CellView component by reference. A change to the data in the Store re-renders the component.

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const App = () => (
  <span>
    <CellView tableId="pets" rowId="fido" cellId="color" store={store} />
  </span>
);

const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>brown</span>'

store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>walnut</span>'

This example creates a Provider context into which a default Store is provided. The CellView component within it then renders the Cell (with its Id for readability).

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>
    <CellView tableId="pets" rowId="fido" cellId="color" debugIds={true} />
  </span>
);

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>color:{brown}</span>'

This example creates a Provider context into which a default Store is provided. The CellView component within it then attempts to render a non-existent Cell.

const App = ({store}) => (
  <Provider store={store}>
    <Pane />
  </Provider>
);
const Pane = () => (
  <span>
    <CellView tableId="pets" rowId="fido" cellId="height" />
  </span>
);

const store = createStore().setCell('pets', 'fido', 'color', 'brown');
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span></span>'

Type aliases

These are the type aliases within the ui-react module.

Checkpoints type aliases

This is the collection of checkpoints type aliases within the ui-react module. There is only one type alias, UndoOrRedoInformation.

UndoOrRedoInformation

The UndoOrRedoInformation type is an array that you can fetch from a Checkpoints object to that indicates if and how you can move the state of the underlying Store forward or backward.

[boolean, Callback, Id | undefined, string]

This type is useful if you are building undo or redo buttons. See the useUndoInformation hook and the useRedoInformation hook for more details and examples.

Component type aliases

This is the collection of component type aliases within the ui-react module. There is only one type alias, ComponentReturnType.

ComponentReturnType

ComponentReturnType is a simple alias for what a React component can return: either a ReactElement, or null for an empty component.

ReactElement<any, any> | null

Identity type aliases

This is the collection of identity type aliases within the ui-react module. There are 5 identity type aliases in total.

StoreOrStoreId

The StoreOrStoreId type is used when you need to refer to a Store in a React hook or component.

Store | Id

In some simple cases you will already have a direct reference to the Store.

This module also includes a Provider component that can be used to wrap multiple Store objects into a context that can be used throughout the app. In this case you will want to refer to a Store by its Id in that context.

Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.

CheckpointsOrCheckpointsId

The CheckpointsOrCheckpointsId type is used when you need to refer to a Checkpoints object in a React hook or component.

Checkpoints | Id

In some simple cases you will already have a direct reference to the Checkpoints object.

This module also includes a Provider component that can be used to wrap multiple Checkpoints objects into a context that can be used throughout the app. In this case you will want to refer to a Checkpoints object by its Id in that context.

Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.

IndexesOrIndexesId

The IndexesOrIndexesId type is used when you need to refer to a Indexes object in a React hook or component.

Indexes | Id

In some simple cases you will already have a direct reference to the Indexes object.

This module also includes a Provider component that can be used to wrap multiple Indexes objects into a context that can be used throughout the app. In this case you will want to refer to an Indexes object by its Id in that context.

Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.

MetricsOrMetricsId

The MetricsOrMetricsId type is used when you need to refer to a Metrics object in a React hook or component.

Metrics | Id

In some simple cases you will already have a direct reference to the Metrics object.

This module also includes a Provider component that can be used to wrap multiple Metrics objects into a context that can be used throughout the app. In this case you will want to refer to a Metrics object by its Id in that context.

Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.

RelationshipsOrRelationshipsId

The RelationshipsOrRelationshipsId type is used when you need to refer to a Relationships object in a React hook or component.

Relationships | Id

In some simple cases you will already have a direct reference to the Relationships object.

This module also includes a Provider component that can be used to wrap multiple Relationships objects into a context that can be used throughout the app. In this case you will want to refer to a Relationships object by its Id in that context.

Many hooks and components in this ui-react module take this type as a parameter or a prop, allowing you to pass in either the Store or its Id.

Props type aliases

This is the collection of props type aliases within the ui-react module. There are 16 props type aliases in total.

TablesProps

TablesProps props are used for components that refer to all the Tables in a Store, such as the TablesView component.

{
  store?: StoreOrStoreId;
  tableComponent?: ComponentType<TableProps>;
  getTableComponentProps?: (tableId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
store?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

tableComponent?ComponentType<TableProps>

A component for rendering each Table in the Store (to override the default TableView component).

getTableComponentProps?getTableComponentProps(tableId: string): ExtraProps

A custom function for generating extra props for each Table component based on its Id.

separator?ReactElement | string

A component or string to separate each Table component.

debugIds?boolean

Whether the component should also render the Ids of the Table, and its descendent objects, to assist with debugging.

TableProps

TableProps props are used for components that refer to a single Table in a Store, such as the TableView component.

{
  tableId: Id;
  store?: StoreOrStoreId;
  rowComponent?: ComponentType<RowProps>;
  getRowComponentProps?: (rowId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
tableIdId

The Id of the Table in the Store to be rendered.

store?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

rowComponent?ComponentType<RowProps>

A custom component for rendering each Row in the Table (to override the default RowView component).

getRowComponentProps?getRowComponentProps(rowId: string): ExtraProps

A function for generating extra props for each custom Row component based on its Id.

separator?ReactElement | string

A component or string to separate each Row component.

debugIds?boolean

Whether the component should also render the Id of the Table, and its descendent objects, to assist with debugging.

RowProps

RowProps props are used for components that refer to a single Row in a Table, such as the RowView component.

{
  tableId: Id;
  rowId: Id;
  store?: StoreOrStoreId;
  cellComponent?: ComponentType<CellProps>;
  getCellComponentProps?: (cellId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
tableIdId

The Id of the Table in the Store.

rowIdId

The Id of the Row in the Table to be rendered.

store?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

cellComponent?ComponentType<CellProps>

A custom component for rendering each Cell in the Row (to override the default CellView component).

getCellComponentProps?getCellComponentProps(cellId: string): ExtraProps

A function for generating extra props for each custom Cell component based on its Id.

separator?ReactElement | string

A component or string to separate each Cell component.

debugIds?boolean

Whether the component should also render the Id of the Row, and its descendent objects, to assist with debugging.

CellProps

RowProps props are used for components that refer to a single Cell in a Row, such as the CellView component.

{
  tableId: Id;
  rowId: Id;
  cellId: Id;
  store?: StoreOrStoreId;
  debugIds?: boolean;
}
TypeDescription
tableIdId

The Id of the Table in the Store.

rowIdId

The Id of the Row in the Table.

cellIdId

The Id of the Cell in the Row to be rendered.

store?StoreOrStoreId

The Store to be accessed: omit for the default context Store, provide an Id for a named context Store, or provide an explicit reference.

debugIds?boolean

Whether the component should also render the Id of the Cell to assist with debugging.

MetricProps

MetricProps props are used for components that refer to a single Metric in a Metrics object, such as the MetricView component.

{
  metricId: Id;
  metrics?: MetricsOrMetricsId;
  debugIds?: boolean;
}
TypeDescription
metricIdId

The Id of the Metric in the Metrics object to be rendered.

metrics?MetricsOrMetricsId

The Metrics object to be accessed: omit for the default context Metrics object, provide an Id for a named context Metrics object, or provide an explicit reference.

debugIds?boolean

Whether the component should also render the Id of the Metric to assist with debugging.

IndexProps

IndexProps props are used for components that refer to a single Index in an Indexes object, such as the IndexView component.

{
  indexId: Id;
  indexes?: IndexesOrIndexesId;
  sliceComponent?: ComponentType<SliceProps>;
  getSliceComponentProps?: (sliceId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
indexIdId

The Id of the Index in the Indexes object to be rendered.

indexes?IndexesOrIndexesId

The Indexes object to be accessed: omit for the default context Indexes object, provide an Id for a named context Indexes object, or provide an explicit reference.

sliceComponent?ComponentType<SliceProps>

A component for rendering each Slice in the Index.

getSliceComponentProps?getSliceComponentProps(sliceId: string): ExtraProps

A function for generating extra props for each Slice component based on its Id.

separator?ReactElement | string

A component or string to separate each Slice component.

debugIds?boolean

Whether the component should also render the Id of the Index, and its descendent objects, to assist with debugging.

SliceProps

IndexProps props are used for components that refer to a single Slice in an Index object, such as the SliceView component.

{
  indexId: Id;
  sliceId: Id;
  indexes?: IndexesOrIndexesId;
  rowComponent?: ComponentType<RowProps>;
  getRowComponentProps?: (rowId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
indexIdId

The Id of the Index in the Indexes object.

sliceIdId

The Id of the Slice in the Index to be rendered.

indexes?IndexesOrIndexesId

The Indexes object to be accessed: omit for the default context Indexes object, provide an Id for a named context Indexes object, or provide an explicit reference.

rowComponent?ComponentType<RowProps>

A component for rendering each Row in the Index.

getRowComponentProps?getRowComponentProps(rowId: string): ExtraProps

A function for generating extra props for each Row component based on its Id.

separator?ReactElement | string

A component or string to separate each Row component.

debugIds?boolean

Whether the component should also render the Id of the Slice, and its descendent objects, to assist with debugging.

LinkedRowsProps

LinkedRowsProps props are used for components that refer to a single Relationship in an Relationships object, and where you want to render a linked list of Rows starting from a first Row, such as the LinkedRowsView component.

{
  relationshipId: Id;
  firstRowId: Id;
  relationships?: RelationshipsOrRelationshipsId;
  rowComponent?: ComponentType<RowProps>;
  getRowComponentProps?: (rowId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
relationshipIdId

The Id of the Relationship in the Relationships object.

firstRowIdId

The Id of the first Row in the linked list Relationship.

relationships?RelationshipsOrRelationshipsId

The Relationships object to be accessed: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

rowComponent?ComponentType<RowProps>

A component for rendering each (remote, local, or linked) Row in the Relationship.

getRowComponentProps?getRowComponentProps(rowId: string): ExtraProps

A function for generating extra props for each Row component based on its Id.

separator?ReactElement | string

A component or string to separate each Row component.

debugIds?boolean

Whether the component should also render the Id of the Row in the Relationship, and its descendent objects, to assist with debugging.

LocalRowsProps

LocalRowsProps props are used for components that refer to a single Relationship in an Relationships object, and where you want to render local Rows based on a remote Row, such as the LocalRowsView component.

{
  relationshipId: Id;
  remoteRowId: Id;
  relationships?: RelationshipsOrRelationshipsId;
  rowComponent?: ComponentType<RowProps>;
  getRowComponentProps?: (rowId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
relationshipIdId

The Id of the Relationship in the Relationships object.

remoteRowIdId

The Id of the remote Row for which to render the local Rows.

relationships?RelationshipsOrRelationshipsId

The Relationships object to be accessed: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

rowComponent?ComponentType<RowProps>

A component for rendering each (remote, local, or linked) Row in the Relationship.

getRowComponentProps?getRowComponentProps(rowId: string): ExtraProps

A function for generating extra props for each Row component based on its Id.

separator?ReactElement | string

A component or string to separate each Row component.

debugIds?boolean

Whether the component should also render the Id of the Row in the Relationship, and its descendent objects, to assist with debugging.

RemoteRowProps

RemoteRowProps props are used for components that refer to a single Relationship in an Relationships object, and where you want to render a remote Row based on a local Row, such as in the RemoteRowView component.

{
  relationshipId: Id;
  localRowId: Id;
  relationships?: RelationshipsOrRelationshipsId;
  rowComponent?: ComponentType<RowProps>;
  getRowComponentProps?: (rowId: string): ExtraProps;
  debugIds?: boolean;
}
TypeDescription
relationshipIdId

The Id of the Relationship in the Relationships object.

localRowIdId

The Id of the local Row for which to render the remote Row.

relationships?RelationshipsOrRelationshipsId

The Relationships object to be accessed: omit for the default context Relationships object, provide an Id for a named context Relationships object, or provide an explicit reference.

rowComponent?ComponentType<RowProps>

A component for rendering each (remote, local, or linked) Row in the Relationship.

getRowComponentProps?getRowComponentProps(rowId: string): ExtraProps

A function for generating extra props for each Row component based on its Id.

debugIds?boolean

Whether the component should also render the Id of the Row in the Relationship, and its descendent objects, to assist with debugging.

BackwardCheckpointsProps

BackwardCheckpointsProps props are used for components that refer to a list of previous checkpoints in a Checkpoints object, such as the BackwardCheckpointsView component.

{
  checkpoints?: CheckpointsOrCheckpointsId;
  checkpointComponent?: ComponentType<CheckpointProps>;
  getCheckpointComponentProps?: (checkpointId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
checkpoints?CheckpointsOrCheckpointsId

The Checkpoints object to be accessed: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

checkpointComponent?ComponentType<CheckpointProps>

A component for rendering each checkpoint in the Checkpoints object.

getCheckpointComponentProps?getCheckpointComponentProps(checkpointId: string): ExtraProps

A function for generating extra props for each checkpoint component based on its Id.

separator?ReactElement | string

A component or string to separate each Checkpoint component.

debugIds?boolean

Whether the component should also render the Ids of the checkpoints to assist with debugging.

CurrentCheckpointProps

CurrentCheckpointsProps props are used for components that refer to the current checkpoints in a Checkpoints object, such as the BackwardCheckpointsView component.

{
  checkpoints?: CheckpointsOrCheckpointsId;
  checkpointComponent?: ComponentType<CheckpointProps>;
  getCheckpointComponentProps?: (checkpointId: string): ExtraProps;
  debugIds?: boolean;
}
TypeDescription
checkpoints?CheckpointsOrCheckpointsId

The Checkpoints object to be accessed: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

checkpointComponent?ComponentType<CheckpointProps>

A component for rendering each checkpoint in the Checkpoints object.

getCheckpointComponentProps?getCheckpointComponentProps(checkpointId: string): ExtraProps

A function for generating extra props for each checkpoint component based on its Id.

debugIds?boolean

Whether the component should also render the Ids of the checkpoints to assist with debugging.

ForwardCheckpointsProps

ForwardCheckpointsProps props are used for components that refer to a list of future checkpoints in a Checkpoints object, such as the ForwardCheckpointsView component.

{
  checkpoints?: CheckpointsOrCheckpointsId;
  checkpointComponent?: ComponentType<CheckpointProps>;
  getCheckpointComponentProps?: (checkpointId: string): ExtraProps;
  separator?: ReactElement | string;
  debugIds?: boolean;
}
TypeDescription
checkpoints?CheckpointsOrCheckpointsId

The Checkpoints object to be accessed: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

checkpointComponent?ComponentType<CheckpointProps>

A component for rendering each checkpoint in the Checkpoints object.

getCheckpointComponentProps?getCheckpointComponentProps(checkpointId: string): ExtraProps

A function for generating extra props for each checkpoint component based on its Id.

separator?ReactElement | string

A component or string to separate each Checkpoint component.

debugIds?boolean

Whether the component should also render the Ids of the checkpoints to assist with debugging.

ProviderProps

ProviderProps props are used with the Provider component, so that Store Metrics, Indexes, Relationships, and Checkpoints objects can be passed into the context of an application and used throughout.

{
  store?: Store;
  storesById?: {[storeId: Id]: Store};
  metrics?: Metrics;
  metricsById?: {[metricsId: Id]: Metrics};
  indexes?: Indexes;
  indexesById?: {[indexesId: Id]: Indexes};
  relationships?: Relationships;
  relationshipsById?: {[relationshipsId: Id]: Relationships};
  checkpoints?: Checkpoints;
  checkpointsById?: {[checkpointsId: Id]: Checkpoints};
}
TypeDescription
store?Store

A default single Store object that will be available within the Provider context.

storesById?{[storeId: Id]: Store}

An object containing multiple Store objects that will be available within the Provider context by their Id.

metrics?Metrics

A default single Metrics object that will be available within the Provider context.

metricsById?{[metricsId: Id]: Metrics}

An object containing multiple Metrics objects that will be available within the Provider context by their Id.

indexes?Indexes

A default single Indexes object that will be available within the Provider context.

indexesById?{[indexesId: Id]: Indexes}

An object containing multiple Indexes objects that will be available within the Provider context by their Id.

relationships?Relationships

A default single Relationships object that will be available within the Provider context.

relationshipsById?{[relationshipsId: Id]: Relationships}

An object containing multiple Relationships objects that will be available within the Provider context by their Id.

checkpoints?Checkpoints

A default single Checkpoints object that will be available within the Provider context.

checkpointsById?{[checkpointsId: Id]: Checkpoints}

An object containing multiple Checkpoints objects that will be available within the Provider context by their Id.

One of each type of object can be provided as a default within the context. Additionally, multiple of each type of object can be provided in an Id-keyed map to the ___ById props.

ExtraProps

The ExtraProps type represents a set of arbitrary additional props.

{[propName: string]: any}
CheckpointProps

CheckpointProps props are used for components that refer to a single checkpoint in an Checkpoints object, such as the CheckpointView component.

{
  checkpointId: Id;
  checkpoints?: CheckpointsOrCheckpointsId;
  debugIds?: boolean;
}
TypeDescription
checkpointIdId

The Id of the checkpoint in the Checkpoints object.

checkpoints?CheckpointsOrCheckpointsId

The Checkpoints object to be accessed: omit for the default context Checkpoints object, provide an Id for a named context Checkpoints object, or provide an explicit reference.

debugIds?boolean

Whether the component should also render the Id of the checkpoint to assist with debugging.

common

The common module of the TinyBase project provides a small collection of common types used across other modules.

Functions

There is one function, defaultSorter, within the common module.

defaultSorter

The defaultSorter function is provided as a convenience to sort keys alphanumerically, and can be provided to the sliceIdSorter and rowIdSorter parameters of the setIndexDefinition method in the indexes module, for example.

defaultSorter(
  sortKey1: SortKey,
  sortKey2: SortKey,
): number
TypeDescription
sortKey1SortKey

The first item of the pair to compare.

sortKey2SortKey

The second item of the pair to compare.

returnsnumber

A number indicating how to sort the pair.

Examples

This example creates an Indexes object.

const store = createStore();
const indexes = createIndexes(store);
console.log(indexes.getIndexIds());
// -> []

This example creates a Store, creates an Indexes object, and defines an Index based on the first letter of the pets' names. The Slice Ids (and Row Ids within them) are alphabetically sorted using the defaultSorter function.

const store = createStore().setTable('pets', {
  fido: {species: 'dog'},
  felix: {species: 'cat'},
  cujo: {species: 'dog'},
});

const indexes = createIndexes(store);
indexes.setIndexDefinition(
  'byFirst', //              indexId
  'pets', //                 tableId
  (_, rowId) => rowId[0], // each Row's Slice Id
  (_, rowId) => rowId, //    each Row's sort key
  defaultSorter, //          sort Slice Ids
  defaultSorter, //          sort Row Ids by sort key
);

console.log(indexes.getSliceIds('byFirst'));
// -> ['c', 'f']
console.log(indexes.getSliceRowIds('byFirst', 'f'));
// -> ['felix', 'fido']

Type aliases

These are the type aliases within the common module.

Callback type aliases

This is the collection of callback type aliases within the common module. There are only two callback type aliases, Callback and ParameterizedCallback.

Callback

The Callback type represents a function that is used as a callback and which does not take a parameter.

(): void
ParameterizedCallback

The ParameterizedCallback type represents a generic function that will take an optional parameter - such as the handler of a DOM event.

(parameter?: Parameter): void
TypeDescription
parameter?Parameter
returnsvoid

This has no return value.

General type aliases

This is the collection of general type aliases within the common module. There is only one type alias, Json.

Json

The Json type is a simple alias for a string, but is used to indicate that the string should be considered to be a JSON serialization of an object.

string

Identity type aliases

This is the collection of identity type aliases within the common module. There are only three identity type aliases, Id, IdOrNull, and Ids.

Id

The Id type is a simple alias for a string, but is used to indicate that the string should be considered to be the key of an object (such as a Row Id string used in a Table).

string
IdOrNull

The Id type is a simple alias for the union of a string or null value, where the string should be considered to be the key of an objects (such as a Row Id string used in a Table), and typically null indicates a wildcard - such as when used in the Store addRowListener method.

Id | null
Ids

The Ids type is a simple alias for an array of strings, but is used to indicate that the strings should be considered to be the keys of objects (such as the Row Id strings used in a Table).

Id[]

Parameter type aliases

This is the collection of parameter type aliases within the common module. There is only one type alias, SortKey.

SortKey

The SortKey type represents a value that can be used by a sort function.

string | number | boolean