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 ofTable
objects. - Each
Table
contains a number ofRow
objects. - Each
Row
contains a number ofCell
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.
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:
Checking existence | Iterator | |
---|---|---|
Table | hasTable | forEachTable |
Row | hasRow | forEachRow |
Cell | hasCell | forEachCell |
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
returns | Tables | 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
hasTables
The hasTables
method returns a boolean indicating whether any Table
objects exist in the Store
.
hasTables(): boolean
returns | boolean | 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
returns | Ids | 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
Type | Description | |
---|---|---|
tableId | string | |
returns | Table | 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
Type | Description | |
---|---|---|
tableId | string | |
returns | boolean | 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
Type | Description | |
---|---|---|
tableId | string | |
returns | Ids | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | Row | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | boolean | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | Ids | 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,
): undefined | Cell
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
returns | undefined | Cell | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
returns | boolean | 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
returns | string | 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
returns | string | 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 9 setter methods in total.
setTables
The setTables
method takes an object and sets the entire data of the Store
.
setTables(tables: Tables): 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 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
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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
row | Row | The data of a single |
returns | Store | 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
Type | Description | |
---|---|---|
tableId | string | |
row | Row | The data of a single |
returns | undefined | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
partialRow | Row | The partial data of a single |
returns | Store | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
cell | Cell | MapCell | The value of the |
returns | Store | 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
Type | Description | |
---|---|---|
json | string | |
returns | Store | 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
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}}}
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): Return
Type | Description | |
---|---|---|
actions | () => Return | The function to be executed as a transaction. |
returns | Return | 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 'b'
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.
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.
Listener methods
This is the collection of listener methods within the Store
interface. There are 9 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
Type | Description | |
---|---|---|
listener | TablesListener | The function that will be called whenever data in the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | 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
Type | Description | |
---|---|---|
listener | TableIdsListener | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | TableListener | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener | The function that will be called whenever the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener | The function that will be called whenever data in the matching |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
returns | string | 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);
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
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
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Store | 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
Type | Description | |
---|---|---|
tableCallback | TableCallback | The function that should be called for every |
returns | void | 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
Type | Description | |
---|---|---|
tableId | string | |
rowCallback | RowCallback | The function that should be called for every |
returns | void | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellCallback | CellCallback | The function that should be called for every |
returns | void | 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'
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
returns | Store | 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
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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
returns | Store | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
forceDel? | boolean | An optional flag to indicate that the whole |
returns | Store | 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
returns | Store | 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
returns | StoreListenerStats | 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
returns | Store | 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 9 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | 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
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
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
returns | void | 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
returns | void | 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: Cell | undefined, newCell: Cell | undefined]
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
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
newCell | Cell | The new value of the |
oldCell | Cell | The old value of the |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | 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
Type | Description | |
---|---|---|
tableId | Id | |
rowId | Id | |
cellId | Id | |
returns | CellChange | 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.
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
Type | Description | |
---|---|---|
tableId | Id | |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the |
returns | void | 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
Type | Description | |
---|---|---|
rowId | Id | |
forEachCell | (cellCallback: CellCallback) => void | |
returns | void | 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
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) => Cell | undefined
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: Cell | undefined) => Cell
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 4 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';
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;
}
Type | Description | |
---|---|---|
tables? | number | The number of TablesListeners registered with the |
tableIds? | number | The number of TableIdsListeners registered with the |
table? | number | The number of TableListeners registered with the |
rowIds? | number | The number of RowIdsListeners registered with the |
row? | number | The number of RowListeners registered with the |
cellIds? | number | The number of CellIdsListeners registered with the |
cell? | number | The number of CellListeners registered with the |
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
returns | Store | 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
Type | Description | |
---|---|---|
metricId | string | |
returns | undefined | 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
Type | Description | |
---|---|---|
metricId | string | |
returns | undefined | 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
returns | Ids | 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
Type | Description | |
---|---|---|
metricId | string | |
returns | boolean | 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
Type | Description | |
---|---|---|
metricId | IdOrNull | |
listener | MetricListener | The function that will be called whenever the |
returns | string | 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
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Metrics | 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
Type | Description | |
---|---|---|
metricId | string | |
returns | Metrics | 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
Type | Description | |
---|---|---|
metricId | string | |
tableId | string | |
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 |
getNumber? | string | (getCell: GetCell, rowId: string) => number | Either the |
aggregateAdd? | AggregateAdd | A function that can be used to optimize a custom |
aggregateRemove? | AggregateRemove | A function that can be used to optimize a custom |
aggregateReplace? | AggregateReplace | A function that can be used to optimize a custom |
returns | Metrics | 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
Type | Description | |
---|---|---|
metricCallback | MetricCallback | The function that should be called for every |
returns | void | 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
returns | MetricsListenerStats | 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
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
Type | Description | |
---|---|---|
metrics | Metrics | A reference to the |
metricId | Id | |
newMetric | Metric | undefined | The new value of the |
oldMetric | Metric | undefined | The old value of the |
returns | void | 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 or numbers and returns an aggregate that is used as a Metric
.
(numbers: number[], length: number) => Metric
Type | Description | |
---|---|---|
numbers | number[] | The array of numbers in the |
length | number | The length of the array of numbers in the |
returns | Metric | 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
Type | Description | |
---|---|---|
metric | Metric | The current value of the |
add | number | The number being added to the |
length | number | The length of the array of numbers in the |
returns | Metric | 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
Type | Description | |
---|---|---|
metric | Metric | The current value of the |
remove | number | The number being removed from the |
length | number | The length of the array of numbers in the |
returns | Metric | 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
Type | Description | |
---|---|---|
metric | Metric | The current value of the |
add | number | The number being added to the |
remove | number | The number being removed from the |
length | number | The length of the array of numbers in the |
returns | Metric | 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
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}
Type | Description | |
---|---|---|
metric? | number | The number of MetricListeners registered with the |
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
returns | Store | 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
Type | Description | |
---|---|---|
indexId | string | |
returns | string | 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
Type | Description | |
---|---|---|
indexId | string | |
sliceId | string | |
returns | Ids | 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
returns | Ids | 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
Type | Description | |
---|---|---|
indexId | string | |
returns | Ids | 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
Type | Description | |
---|---|---|
indexId | string | |
returns | boolean | 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
Type | Description | |
---|---|---|
indexId | string | |
sliceId | string | |
returns | boolean | 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
Type | Description | |
---|---|---|
indexId | IdOrNull | |
sliceId | IdOrNull | |
listener | SliceRowIdsListener | The function that will be called whenever the |
returns | string | 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
Type | Description | |
---|---|---|
indexId | IdOrNull | |
listener | SliceIdsListener | The function that will be called whenever the |
returns | string | 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
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Indexes | 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
Type | Description | |
---|---|---|
indexId | string | |
returns | Indexes | 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
Type | Description | |
---|---|---|
indexId | string | |
tableId | string | |
getSliceId? | string | (getCell: GetCell, rowId: string) => string | Either the |
getSortKey? | string | (getCell: GetCell, rowId: string) => SortKey | Either the |
sliceIdSorter? | (sliceId1: string, sliceId2: string) => number | A function that takes two |
rowIdSorter? | (sortKey1: SortKey, sortKey2: SortKey, sliceId: string) => number | A function that takes two |
returns | Indexes | 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
Type | Description | |
---|---|---|
indexCallback | IndexCallback | The function that should be called for every |
returns | void | 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
Type | Description | |
---|---|---|
indexId | string | |
sliceCallback | SliceCallback | The function that should be called for every |
returns | void | 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
returns | IndexesListenerStats | 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
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
Type | Description | |
---|---|---|
indexes | Indexes | A reference to the |
indexId | Id | |
sliceId | Id | |
returns | void | 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
Type | Description | |
---|---|---|
indexes | Indexes | A reference to the |
indexId | Id | |
returns | void | 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
Type | Description | |
---|---|---|
indexId | Id | |
forEachSlice | (sliceCallback: SliceCallback) => void | |
returns | void | 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
Type | Description | |
---|---|---|
sliceId | Id | |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the |
returns | void | 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;
}
Type | Description | |
---|---|---|
sliceIds? | number | The number of SlideIdsListeners registered with the |
sliceRowIds? | number | The number of SliceRowIdsListeners registered with the |
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
returns | Store | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
returns | string | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
returns | string | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
firstRowId | string | The |
returns | Ids | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
remoteRowId | string | The |
returns | Ids | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
localRowId | string | The |
returns | undefined | 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
returns | Ids | 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
Type | Description | |
---|---|---|
indexId | string | |
returns | boolean | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
firstRowId | string | |
listener | LinkedRowIdsListener | The function that will be called whenever the linked |
returns | string | 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
Type | Description | |
---|---|---|
relationshipId | IdOrNull | The |
remoteRowId | IdOrNull | The |
listener | LocalRowIdsListener | The function that will be called whenever the local |
returns | string | 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
Type | Description | |
---|---|---|
relationshipId | IdOrNull | The |
localRowId | IdOrNull | The |
listener | RemoteRowIdListener | The function that will be called whenever the remote |
returns | string | 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
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Relationships | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
returns | Relationships | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
localTableId | string | The |
remoteTableId | string | The |
getRemoteRowId | string | (getCell: GetCell, localRowId: string) => string | Either the |
returns | Relationships | 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
Type | Description | |
---|---|---|
relationshipCallback | RelationshipCallback | The function that should be called for every |
returns | void | 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
returns | RelationshipsListenerStats | 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
Type | Description | |
---|---|---|
store | Store | The |
returns | Relationships | 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
Type | Description | |
---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
firstRowId | Id | The |
returns | void | 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
Type | Description | |
---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
remoteRowId | Id | |
returns | void | 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
Type | Description | |
---|---|---|
relationships | Relationships | A reference to the |
relationshipId | Id | The |
localRowId | Id | |
returns | void | 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
Type | Description | |
---|---|---|
relationshipId | Id | The |
forEachRow | (rowCallback: RowCallback) => void | A function that will let you iterate over the local |
returns | void | 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};
}
Type | Description | |
---|---|---|
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;
}
Type | Description | |
---|---|---|
remoteRowId? | number | The number of RemoteRowIdListeners registered with the |
localRowIds? | number | The number of LocalRowIdsListeners registered with the |
linkedRowIds? | number | The number of LinkedRowIds registered with the |
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
returns | Store | 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
Type | Description | |
---|---|---|
checkpointId | string | The |
returns | undefined | 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
returns | CheckpointIds | 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
Type | Description | |
---|---|---|
checkpointId | string | The |
returns | boolean | 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
Type | Description | |
---|---|---|
label? | string | An optional label to describe the actions leading up to this checkpoint. |
returns | string | 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
Type | Description | |
---|---|---|
checkpointId | string | The |
label | string | A label to describe the actions leading up to this checkpoint or left undefined if you want to clear the current label. |
returns | Checkpoints | 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
Type | Description | |
---|---|---|
listener | CheckpointIdsListener | The function that will be called whenever the checkpoints change. |
returns | string | 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
Type | Description | |
---|---|---|
checkpointId | IdOrNull | The |
listener | CheckpointListener | The function that will be called whenever the checkpoint label changes. |
returns | string | 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
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Checkpoints | 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
Type | Description | |
---|---|---|
size | number | The number of checkpoints that this |
returns | Checkpoints | 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
Type | Description | |
---|---|---|
checkpointCallback | CheckpointCallback | The function that should be called for every Checkpoint. |
returns | void | 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
returns | Checkpoints | 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
returns | Checkpoints | 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
returns | Checkpoints | 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
Type | Description | |
---|---|---|
checkpointId | string | The |
returns | Checkpoints | 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
returns | CheckpointsListenerStats | 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
Type | Description | |
---|---|---|
store | Store | The |
returns | Checkpoints | 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
Type | Description | |
---|---|---|
checkpoints | Checkpoints | A reference to the |
returns | void | 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
Type | Description | |
---|---|---|
checkpoints | Checkpoints | A reference to the |
checkpointId | Id | The |
returns | void | 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
Type | Description | |
---|---|---|
checkpointId | Id | The |
label? | string | |
returns | void | 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 thisStore
). They are in chronological order with the oldest checkpoint at the start of the array. - The current checkpoint
Id
of theStore
's state, orundefined
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 thisStore
). 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;
}
Type | Description | |
---|---|---|
checkpointIds? | number | The number of CheckpointIdsListeners registered with the |
checkpoint? | number | The number of CheckpointListeners registered with the |
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
:
- The
createSessionPersister
function returns aPersister
that uses the browser's session storage. - The
createLocalPersister
function returns aPersister
that uses the browser's local storage. - The
createRemotePersister
function returns aPersister
that uses a remote server. - The
createFilePersister
function returns aPersister
that uses a local file (in an appropriate environment).
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
returns | Store | 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
returns | Persister |
---|
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>
Type | Description | |
---|---|---|
initialTables? | Tables | An optional |
returns | Promise<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>
Type | Description | |
---|---|---|
initialTables? | Tables | An optional |
returns | Promise<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
returns | Persister | 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>
returns | Promise<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>
returns | Promise<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
returns | Persister | 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
returns | PersisterStats | 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
Type | Description | |
---|---|---|
store | Store | The |
getPersisted | () => Promise<undefined | null | string> | An asynchronous function which will fetch JSON from the persistence layer (or |
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 |
stopListeningToPersisted | Callback | A function that will unregister the listener from the underlying changes to the persistence layer. |
returns | Persister | 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
Type | Description | |
---|---|---|
store | Store | The |
filePath | string | The location of the local file to persist the |
returns | Persister | 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
Type | Description | |
---|---|---|
store | Store | The |
storageName | string | The unique key to identify the storage location. |
returns | Persister | 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
Type | Description | |
---|---|---|
store | Store | The |
loadUrl | string | The endpoint that supports a |
saveUrl | string | The endpoint that supports a |
autoLoadIntervalSeconds | number | How often to poll the |
returns | Persister | 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
Type | Description | |
---|---|---|
store | Store | The |
storageName | string | The unique key to identify the storage location. |
returns | Persister | 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;
}
Type | Description | |
---|---|---|
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
Type | Description | |
---|---|---|
checkpointId | string | The |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | string | 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
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | CheckpointIds | 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
Type | Description | |
---|---|---|
listener | CheckpointIdsListener | The function that will be called whenever the checkpoints change. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | void | 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
Type | Description | |
---|---|---|
checkpointId | IdOrNull | The |
listener | CheckpointListener | The function that will be called whenever the checkpoint label changes. |
listenerDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | void | 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
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Checkpoints | 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
create | (store: Store) => Checkpoints | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Checkpoints | 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
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | Callback | 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
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | Callback | 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>
Type | Description | |
---|---|---|
getCheckpointId | (parameter: Parameter) => string | A function which returns an |
getCheckpointIdDeps? | DependencyList | An optional array of dependencies for the |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
then? | (checkpoints: Checkpoints, checkpointId: string) => void | A function which is called after the checkpoint is moved, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<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
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | UndoOrRedoInformation | 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>
Type | Description | |
---|---|---|
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 |
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
then? | (checkpointId: string, checkpoints: Checkpoints, label?: string) => void | A function which is called after the checkpoint is set, with the new checkpoint |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<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
Type | Description | |
---|---|---|
checkpointsOrCheckpointsId? | CheckpointsOrCheckpointsId | The |
returns | UndoOrRedoInformation | 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
Type | Description | |
---|---|---|
indexId | string | |
sliceId | string | |
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | Ids | 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
Type | Description | |
---|---|---|
indexId | IdOrNull | |
sliceId | IdOrNull | |
listener | SliceRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | void | 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
create | (store: Store) => Indexes | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Indexes | 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
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Indexes | 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
Type | Description | |
---|---|---|
indexId | string | |
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | Ids | 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
Type | Description | |
---|---|---|
indexId | IdOrNull | |
listener | SliceIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
indexesOrIndexesId? | IndexesOrIndexesId | The |
returns | void | 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
create | (store: Store) => Metrics | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Metrics | 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
Type | Description | |
---|---|---|
metricId | string | |
metricsOrMetricsId? | MetricsOrMetricsId | The |
returns | number | 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
Type | Description | |
---|---|---|
metricId | IdOrNull | |
listener | MetricListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
metricsOrMetricsId? | MetricsOrMetricsId | The |
returns | void | 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
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Metrics | 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
create | (store: Store) => Persister | A function for performing the creation steps of the |
createDeps? | DependencyList | An optional array of dependencies for the |
then? | (persister: Persister) => Promise<void> | An optional callback for performing asynchronous post-creation steps on the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | Persister | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
firstRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | Ids | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
firstRowId | string | |
listener | LinkedRowIdsListener | The function that will be called whenever the linked |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | void | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
remoteRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | Ids | 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
Type | Description | |
---|---|---|
relationshipId | IdOrNull | The |
remoteRowId | IdOrNull | The |
listener | LocalRowIdsListener | The function that will be called whenever the local |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | void | 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
Type | Description | |
---|---|---|
relationshipId | string | The |
localRowId | string | The |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | Id | 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
Type | Description | |
---|---|---|
relationshipId | IdOrNull | The |
localRowId | IdOrNull | The |
listener | RemoteRowIdListener | The function that will be called whenever the remote |
listenerDeps? | DependencyList | An optional array of dependencies for the |
relationshipsOrRelationshipsId? | RelationshipsOrRelationshipsId | The |
returns | void | 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
Type | Description | |
---|---|---|
store | Store | A reference to the |
create | (store: Store) => Relationships | An optional callback for performing post-creation steps on the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Relationships | 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
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Relationships | 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
Type | Description | |
---|---|---|
create | () => Store | A function for performing the creation of the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Store | 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
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Store | 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
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | Callback | 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>
Type | Description | |
---|---|---|
getTables | (parameter: Parameter, store: Store) => Tables | A function which returns the |
getTablesDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, tables: Tables) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<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
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
returns | Tables | 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
Type | Description | |
---|---|---|
listener | TablesListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | 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
Type | Description | |
---|---|---|
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids | 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
Type | Description | |
---|---|---|
listener | TableIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | 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
Type | Description | |
---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | Callback | 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>
Type | Description | |
---|---|---|
tableId | string | |
getTable | (parameter: Parameter, store: Store) => Table | A function which returns the |
getTableDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, table: Table) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<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
Type | Description | |
---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Table | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | TableListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | 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
Type | Description | |
---|---|---|
tableId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
listener | RowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | 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>
Type | Description | |
---|---|---|
tableId | string | |
getRow | (parameter: Parameter, store: Store) => Row | A function which returns the |
getRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (rowId: undefined | string, store: Store, row: Row) => void | A function which is called after the mutation, with the new |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | Callback | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Row | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | RowListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | 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>
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
getPartialRow | (parameter: Parameter, store: Store) => Row | A function which returns the partial |
getPartialRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, partialRow: Row) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<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>
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
getRow | (parameter: Parameter, store: Store) => Row | A function which returns the |
getRowDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, row: Row) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
listener | CellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
storeOrStoreId? | StoreOrStoreId | The |
returns | Cell | 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
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | CellListener | The function that will be called whenever data in the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
mutator? | boolean | An optional boolean that indicates that the listener mutates |
storeOrStoreId? | StoreOrStoreId | The |
returns | void | 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
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
forceDel? | boolean | An optional flag to indicate that the whole |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store) => void | A function which is called after the deletion, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | Callback | 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>
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
getCell | (parameter: Parameter, store: Store) => Cell | MapCell | A function which returns the |
getCellDeps? | DependencyList | An optional array of dependencies for the |
storeOrStoreId? | StoreOrStoreId | The |
then? | (store: Store, cell: Cell | MapCell) => void | A function which is called after the mutation, with a reference to the |
thenDeps? | DependencyList | An optional array of dependencies for the |
returns | ParameterizedCallback<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
Type | Description | |
---|---|---|
props | BackwardCheckpointsProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | CheckpointProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | CurrentCheckpointProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | ForwardCheckpointsProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | ProviderProps & {children: ReactNode} | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | IndexProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | SliceProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | MetricProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | LinkedRowsProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | LocalRowsProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | RemoteRowProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | TablesProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | TableProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | RowProps | The props for this component. |
returns | ComponentReturnType | 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
Type | Description | |
---|---|---|
props | CellProps | The props for this component. |
returns | ComponentReturnType | 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;
}
Type | Description | |
---|---|---|
store? | StoreOrStoreId | The |
tableComponent? | ComponentType<TableProps> | A component for rendering each |
getTableComponentProps? | getTableComponentProps(tableId: string): ExtraProps | A custom function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
tableId | Id | |
store? | StoreOrStoreId | The |
rowComponent? | ComponentType<RowProps> | A custom component for rendering each |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
tableId | Id | |
rowId | Id | |
store? | StoreOrStoreId | The |
cellComponent? | ComponentType<CellProps> | A custom component for rendering each |
getCellComponentProps? | getCellComponentProps(cellId: string): ExtraProps | A function for generating extra props for each custom |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
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;
}
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;
}
Type | Description | |
---|---|---|
metricId | Id | |
metrics? | MetricsOrMetricsId | The |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
indexId | Id | |
indexes? | IndexesOrIndexesId | The |
sliceComponent? | ComponentType<SliceProps> | |
getSliceComponentProps? | getSliceComponentProps(sliceId: string): ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
indexId | Id | |
sliceId | Id | |
indexes? | IndexesOrIndexesId | The |
rowComponent? | ComponentType<RowProps> | |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
relationshipId | Id | The |
firstRowId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
relationshipId | Id | The |
remoteRowId | Id | The |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each |
separator? | ReactElement | string | A component or string to separate each |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
relationshipId | Id | The |
localRowId | Id | |
relationships? | RelationshipsOrRelationshipsId | The |
rowComponent? | ComponentType<RowProps> | A component for rendering each (remote, local, or linked) |
getRowComponentProps? | getRowComponentProps(rowId: string): ExtraProps | A function for generating extra props for each |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | getCheckpointComponentProps(checkpointId: string): ExtraProps | A function for generating extra props for each checkpoint component based on its |
separator? | ReactElement | string | A component or string to separate each Checkpoint component. |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | getCheckpointComponentProps(checkpointId: string): ExtraProps | A function for generating extra props for each checkpoint component based on its |
debugIds? | boolean | Whether the component should also render the |
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;
}
Type | Description | |
---|---|---|
checkpoints? | CheckpointsOrCheckpointsId | The |
checkpointComponent? | ComponentType<CheckpointProps> | A component for rendering each checkpoint in the |
getCheckpointComponentProps? | getCheckpointComponentProps(checkpointId: string): ExtraProps | A function for generating extra props for each checkpoint component based on its |
separator? | ReactElement | string | A component or string to separate each Checkpoint component. |
debugIds? | boolean | Whether the component should also render the |
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};
}
Type | Description | |
---|---|---|
store? | Store | A default single |
storesById? | {[storeId: Id]: Store} | An object containing multiple |
metrics? | Metrics | A default single |
metricsById? | {[metricsId: Id]: Metrics} | An object containing multiple |
indexes? | Indexes | A default single |
indexesById? | {[indexesId: Id]: Indexes} | An object containing multiple |
relationships? | Relationships | A default single |
relationshipsById? | {[relationshipsId: Id]: Relationships} | An object containing multiple |
checkpoints? | Checkpoints | A default single |
checkpointsById? | {[checkpointsId: Id]: Checkpoints} | An object containing multiple |
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;
}
Type | Description | |
---|---|---|
checkpointId | Id | The |
checkpoints? | CheckpointsOrCheckpointsId | The |
debugIds? | boolean | Whether the component should also render the |
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
Type | Description | |
---|---|---|
sortKey1 | SortKey | The first item of the pair to compare. |
sortKey2 | SortKey | The second item of the pair to compare. |
returns | number | 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
Type | Description | |
---|---|---|
parameter? | Parameter | |
returns | void | 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