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.
You can listen to attempts to write invalid data to a Cell
with the addInvalidCellListener
method.
The transaction
method is used to wrap multiple changes to the Store
so that the relevant listeners only fire once.
The setJson
method and the getJson
method allow you to work with a JSON-encoded representation of the entire Store
, which is useful for persisting it.
Finally, the callListener
method provides a way for you to manually provoke a listener to be called, even if the underlying data hasn't changed. This is useful when you are using mutator listeners to guarantee that data conforms to programmatic conditions, and those conditions change such that you need to update the Store
in bulk.
Read more about setting and changing data in The Basics guides, and about listeners in the Listening to Stores guide.
Creating a Schema
You can set a Schema
on a Store
when you create it with createStore
function, or at a later stage with the setSchema
method. A Schema
constrains the Table
Ids
the Store
can have, and the types of Cell
data in each Table
. Each Cell
requires its type to be specified, and can also take a default value for when it's not specified.
You can also get a serialization of the Schema
out of the Store
with the getSchemaJson
method, and remove the Schema
altogether with the delSchema
method.
Read more about schemas in the Using Schemas guide.
Convenience methods
There are a few additional helper methods to make it easier to work with a Store
. There are methods for easily checking the existence of a Table
, Row
, or Cell
, and iterators that let you act on the children of a common parent:
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 14 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.
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.
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'));
// -> []
getSortedRowIds
The getSortedRowIds
method returns the Ids
of every Row
in a given Table
, sorted according to the values in a specified Cell
.
getSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
returns | Ids | An array of the sorted Ids of every Row in the Table. |
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the Table
is large. For a performant approach to tracking the sorted Row
Ids
when they change, use the addSortedRowIdsListener
method.
If the Table
does not exist, an empty array is returned.
Examples
This example retrieves sorted Row
Ids
in a Table
.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species'));
// -> ['felix', 'fido']
This example retrieves sorted Row
Ids
in a Table
in reverse order.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets', 'species', true));
// -> ['cujo', 'fido', 'felix']
This example retrieves two pages of Row
Ids
in a Table
.
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
console.log(store.getSortedRowIds('pets', 'price', false, 0, 2));
// -> ['lowly', 'mickey']
console.log(store.getSortedRowIds('pets', 'price', false, 2, 2));
// -> ['carnaby', 'tom']
This example retrieves Row
Ids
sorted by their own value, since the cellId
parameter is undefined.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'wolf'},
},
});
console.log(store.getSortedRowIds('pets'));
// -> ['cujo', 'felix', 'fido']
This example retrieves the sorted Row
Ids
of a Table
that does not exist, returning an empty array.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
console.log(store.getSortedRowIds('employees'));
// -> []
Since
v2.0.0
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.
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 the value of a single Cell
in a given Row
, in a given Table
.
getCell(
tableId: string,
rowId: string,
cellId: string,
): CellOrUndefined
Type | Description | |
---|---|---|
tableId | string | |
rowId | string | |
cellId | string | |
returns | CellOrUndefined | The value of the Cell, or `undefined`. |
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 8 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}}}
Listener methods
This is the collection of listener methods within the Store
interface. There are 13 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. |
The provided listener is a TableIdsListener
function, and will be called with a reference to the Store
.
By default, 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.
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, // mutator
);
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. |
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.
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).
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. |
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.
By default, 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 a null
wildcard).
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, // mutator
);
store.setRow('pets', 'felix', {species: 'cat'});
console.log(store.getTable('meta'));
// -> {update: {pets: true}}
store.delListener(listenerId);
addSortedRowIdsListener
The addSortedRowIdsListener
method registers a listener function with the Store
that will be called whenever sorted (and optionally, paginated) Row
Ids
in a Table
change.
addSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener | The function that will be called whenever the sorted |
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 SortedRowIdsListener
function, and will be called with a reference to the Store
, the Id
of the Table
whose Row
Ids
sorting changed, the Cell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Such a listener is called when a Row
is added or removed, but also when a value in the specified Cell
(somewhere in the Table
) has changed enough to change the sorting of the Row
Ids
.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified Table
, sorted by a single specified Cell
.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
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 sorted Row
Ids
of a specific Table
.
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'cujo']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'fido', {species: 'dog'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['felix', 'fido', 'cujo']
store.delListener(listenerId);
This 111example registers a listener that responds to any change to a paginated section of the sorted Row
Ids
of a specific Table
.
const store = createStore().setTables({
pets: {
fido: {price: 6},
felix: {price: 5},
mickey: {price: 2},
tom: {price: 4},
carnaby: {price: 3},
lowly: {price: 1},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'price',
false,
0,
3,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`First three sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
console.log(store.getSortedRowIds('pets', 'price', false, 0, 3));
// -> ['lowly', 'mickey', 'carnaby']
store.setCell('pets', 'carnaby', 'price', 4.5);
// -> 'First three sorted Row Ids for pets table changed'
// -> ['lowly', 'mickey', 'tom']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
. The Row
Ids
are sorted by their own value, since the cellId
parameter is explicitly undefined.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', undefined, false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
undefined,
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids for pets table changed'
// -> ['cujo', 'felix', 'fido']
store.delListener(listenerId);
This example registers a listener that responds to a change in the sorting of the rows of a specific Table
, even though the set of Ids
themselves has not changed.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
console.log(store.getSortedRowIds('pets', 'species', false));
// -> ['felix', 'fido']
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for ${tableId} table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setCell('pets', 'felix', 'species', 'tiger');
// -> 'Sorted Row Ids for pets table changed'
// -> ['fido', 'felix']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
, and which also mutates the Store
itself.
const store = createStore().setTables({
pets: {
cujo: {species: 'wolf'},
felix: {species: 'cat'},
},
});
const listenerId = store.addSortedRowIdsListener(
'pets',
'species',
false,
0,
undefined,
(store, tableId) => store.setCell('meta', 'sorted', tableId, true),
true, // mutator
);
store.setRow('pets', 'fido', {species: 'dog'});
console.log(store.getTable('meta'));
// -> {sorted: {pets: true}}
store.delListener(listenerId);
Since
v2.0.0
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. |
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.
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
.
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. |
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.
By default, 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 a null
wildcard).
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
.
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, // mutator
);
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. |
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.
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.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Examples
This example registers a listener that responds to any changes to a specific Cell
.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log('color cell in fido row in pets table changed');
console.log([oldCell, newCell]);
console.log(getCellChange('pets', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any Cell
.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in pets table changed'
store.setTable('species', {dog: {price: 5}});
// -> 'price cell in dog row in species table changed'
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell
, and which also mutates the Store
itself.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId) =>
store.setCell('meta', 'update', `${tableId}_${rowId}_${cellId}`, true),
true,
);
store.delCell('pets', 'fido', 'color');
console.log(store.getTable('meta'));
// -> {update: {pets_fido_color: true}}
store.delListener(listenerId);
addInvalidCellListener
The addInvalidCellListener
method registers a listener function with the Store
that will be called whenever invalid data was attempted to be written to a Cell
.
addInvalidCellListener(
tableId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: InvalidCellListener,
mutator?: boolean,
): string
Type | Description | |
---|---|---|
tableId | IdOrNull | |
rowId | IdOrNull | |
cellId | IdOrNull | |
listener | InvalidCellListener | The function that will be called whenever an attempt to write invalid data to 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. |
The provided listener is an InvalidCellListener
function, and will be called with a reference to the Store
, the Id
of the Table
, the Id
of the Row
, and the Id
of Cell
that were being attempted to be changed. It is also given the invalid value of the Cell
, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell
within a single transaction, this is an array containing each attempt, chronologically.
You can either listen to a single Cell
(by specifying the Table
Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or invalid attempts to change any Cell
(by providing null
wildcards).
All, some, or none of the tableId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific Row
in a specific Table
, any Cell
in any Row
in any Table
, for example - or every other combination of wildcards.
Use the optional mutator parameter to indicate that there is code in the listener that will mutate Store
data. If set to false
(or omitted), such mutations will be silently ignored. All relevant mutator listeners (with this flag set to true
) are called before any non-mutator listeners (since the latter may become relevant due to changes made in the former). The changes made by mutator listeners do not fire other mutating listeners, though they will fire non-mutator listeners.
Special note should be made for how the listener will be called when a Schema
is present. The listener will be called:
- if a
Table
is being updated that is not specified in theSchema
- if a
Cell
is of the wrong type specified in theSchema
- if a
Cell
is omitted and is not defaulted in theSchema
- if an empty
Row
is provided and there are noCell
defaults in theSchema
The listener will not be called if Cell
that is defaulted in the Schema
is not provided, as long as all of the Cells that are not defaulted are provided.
To help understand all of these schema-based conditions, please see the Schema
example below.
Examples
This example registers a listener that responds to any invalid changes to a specific Cell
.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) => {
console.log('Invalid color cell in fido row in pets table');
console.log(invalidCells);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
// -> [{r: '96', g: '4B', b: '00'}]
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell
- in a Store
without a Schema
. Note also how it then responds to cases where an empty or invalid Row
objects, or Table
objects, or Tables
objects are provided.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
// -> 'Invalid color cell in fido row in pets table'
store.setTable('sales', {fido: {date: new Date()}});
// -> 'Invalid date cell in fido row in sales table'
store.setRow('pets', 'felix', {});
// -> 'Invalid undefined cell in felix row in pets table'
store.setRow('filter', 'name', /[a-z]?/);
// -> 'Invalid undefined cell in name row in filter table'
store.setRow('sales', '2021', {forecast: undefined});
// -> 'Invalid forecast cell in 2021 row in sales table'
store.addRow('filter', /[0-9]?/);
// -> 'Invalid undefined cell in undefined row in filter table'
store.setTable('raw', {});
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTable('raw', ['row1', 'row2']);
// -> 'Invalid undefined cell in undefined row in raw table'
store.setTables(['table1', 'table2']);
// -> 'Invalid undefined cell in undefined row in undefined table'
store.delListener(listenerId);
This example registers a listener that responds to any invalid changes to any Cell
- in a Store
with a Schema
. Note how it responds to cases where missing parameters are provided for optional, and defaulted Cell
values in a Row
.
const store = createStore().setSchema({
pets: {
species: {type: 'string'},
color: {type: 'string', default: 'unknown'},
},
});
const listenerId = store.addInvalidCellListener(
null,
null,
null,
(store, tableId, rowId, cellId) => {
console.log(
`Invalid ${cellId} cell in ${rowId} row in ${tableId} table`,
);
},
);
store.setRow('sales', 'fido', {price: 5});
// -> 'Invalid price cell in fido row in sales table'
// The listener is called, because the sales Table is not in the schema
store.setRow('pets', 'felix', {species: true});
// -> 'Invalid species cell in felix row in pets table'
// The listener is called, because species is invalid...
console.log(store.getRow('pets', 'felix'));
// -> {color: 'unknown'}
// ...even though a Row was set with the default value
store.setRow('pets', 'fido', {color: 'brown'});
// -> 'Invalid species cell in fido row in pets table'
// The listener is called, because species is missing and not defaulted...
console.log(store.getRow('pets', 'fido'));
// -> {color: 'brown'}
// ...even though a Row was set
store.setRow('pets', 'rex', {species: 'dog'});
console.log(store.getRow('pets', 'rex'));
// -> {species: 'dog', color: 'unknown'}
// The listener is not called, because color is defaulted
store.delTables().setSchema({
pets: {
species: {type: 'string'},
color: {type: 'string'},
},
});
store.setRow('pets', 'cujo', {});
// -> 'Invalid species cell in cujo row in pets table'
// -> 'Invalid color cell in cujo row in pets table'
// -> 'Invalid undefined cell in cujo row in pets table'
// The listener is called multiple times, because neither Cell is defaulted
// and the Row as a whole is empty
store.delListener(listenerId);
This example registers a listener that responds to any changes to a specific Cell
, and which also mutates the Store
itself.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addInvalidCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, invalidCells) =>
store.setCell(
'meta',
'invalid_updates',
`${tableId}_${rowId}_${cellId}`,
JSON.stringify(invalidCells[0]),
),
true,
);
store.setCell('pets', 'fido', 'color', {r: '96', g: '4B', b: '00'});
console.log(store.getRow('meta', 'invalid_updates'));
// -> {'pets_fido_color': '{"r":"96","g":"4B","b":"00"}'}
store.delListener(listenerId);
Since
v1.1.0
addDidFinishTransactionListener
The addDidFinishTransactionListener
method registers a listener function with the Store
that will be called just after other non-mutating listeners are called at the end of the transaction.
addDidFinishTransactionListener(listener: TransactionListener): string
Type | Description | |
---|---|---|
listener | TransactionListener | |
returns | string | A unique Id for the listener that can later be used to remove it. |
This is useful if you need to know that a set of listeners have just been called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener
will receive a reference to the Store
and a boolean to indicate whether Cell
values have been touched during the transaction. The latter flag is intended as a hint about whether non-mutating listeners might have been called at the end of the transaction.
Here, 'touched' means that Cell
values have either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched
in the listener will be false
because all changes have been reverted.
Example
This example registers a listener that is called at the end of the transaction, just after its listeners have been called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched
parameter in the listener works.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addDidFinishTransactionListener(
(store, cellsTouched) => console.log(`Cells touched: ${cellsTouched}`),
);
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
store.transaction(() => store.setCell('pets', 'fido', 'color', 'brown'));
// -> 'Cells touched: false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Tables changed'
// -> 'Cells touched: true'
store.transaction(() => {
store.setRow('pets', 'felix', {species: 'cat'});
store.delRow('pets', 'felix');
});
// -> 'Cells touched: true'
store.transaction(
() => store.setRow('pets', 'fido', {species: 'dog'}),
() => true,
);
// -> 'Cells touched: false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells touched: undefined'
// It is meaningless to call this listener directly.
store.delListener(listenerId).delListener(listenerId2);
Since
v1.3.0
addWillFinishTransactionListener
The addWillFinishTransactionListener
method registers a listener function with the Store
that will be called just before other non-mutating listeners are called at the end of the transaction.
addWillFinishTransactionListener(listener: TransactionListener): string
Type | Description | |
---|---|---|
listener | TransactionListener | |
returns | string | A unique Id for the listener that can later be used to remove it. |
This is useful if you need to know that a set of listeners are about to be called at the end of a transaction, perhaps to batch their consequences together.
The provided TransactionListener
will receive a reference to the Store
and a boolean to indicate whether Cell
values have been touched during the transaction. The latter flag is intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell
values have either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched
in the listener will be false
because all changes have been reverted.
Example
This example registers a listener that is called at the end of the transaction, just before its listeners will be called. The transactions shown here variously change, touch, and rollback cells, demonstrating how the cellsTouched
parameter in the listener works.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
const listenerId = store.addWillFinishTransactionListener(
(store, cellsTouched) => console.log(`Cells touched: ${cellsTouched}`),
);
const listenerId2 = store.addTablesListener(() =>
console.log('Tables changed'),
);
store.transaction(() => store.setCell('pets', 'fido', 'color', 'brown'));
// -> 'Cells touched: false'
store.transaction(() => store.setCell('pets', 'fido', 'color', 'walnut'));
// -> 'Cells touched: true'
// -> 'Tables changed'
store.transaction(() => {
store.setRow('pets', 'felix', {species: 'cat'});
store.delRow('pets', 'felix');
});
// -> 'Cells touched: true'
store.transaction(
() => store.setRow('pets', 'fido', {species: 'dog'}),
() => true,
);
// -> 'Cells touched: false'
// Transaction was rolled back.
store.callListener(listenerId);
// -> 'Cells touched: undefined'
// It is meaningless to call this listener directly.
store.delListener(listenerId).delListener(listenerId2);
Since
v1.3.0
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'
Transaction methods
This is the collection of transaction methods within the Store
interface. There are only three transaction methods, finishTransaction
, startTransaction
, and transaction
.
finishTransaction
The finishTransaction
method allows you to explicitly finish a transaction that has made multiple mutations to the Store
, triggering all calls to the relevant listeners.
finishTransaction(doRollback?: (changedCells: ChangedCells, invalidCells: InvalidCells) => boolean): Store
Type | Description | |
---|---|---|
doRollback? | (changedCells: ChangedCells, invalidCells: InvalidCells) => boolean | An optional callback that should return |
returns | Store | A reference to the Store. |
Transactions are 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.
Generally it is preferable to use the transaction
method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction
methods for you. See that method for several transaction examples.
Use this finishTransaction
method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. There must have been a corresponding startTransaction
method that this completes, of course, otherwise this function has no effect.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. 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.startTransaction();
store.setCell('pets', 'fido', 'color', 'walnut');
store.setCell('pets', 'fido', 'sold', true);
store.finishTransaction();
// -> 'Fido changed'
This example makes multiple changes to the Store
, including some attempts to update a Cell
with invalid values. The doRollback
callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.startTransaction();
store.setCell('pets', 'fido', 'color', 'black');
store.setCell('pets', 'fido', 'eyes', ['left', 'right']);
store.setCell('pets', 'fido', 'info', {sold: null});
store.finishTransaction((changedCells, invalidCells) => {
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(changedCells);
// -> {pets: {fido: {color: ['brown', 'black']}}}
console.log(invalidCells);
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
return invalidCells['pets'] != null;
});
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
Since
v1.3.0
startTransaction
The startTransaction
method allows you to explicitly start a transaction that will make multiple mutations to the Store
, buffering all calls to the relevant listeners until it completes when you call the finishTransaction
method.
startTransaction(): Store
returns | Store | A reference to the Store. |
---|
Transactions are 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.
Generally it is preferable to use the transaction
method to wrap a block of code as a transaction. It simply calls both the startTransaction and finishTransaction
methods for you. See that method for several transaction examples.
Use this startTransaction
method when you have a more 'open-ended' transaction, such as one containing mutations triggered from other events that are asynchronous or not occurring inline to your code. You must remember to also call the finishTransaction
method explicitly when it is done, of course.
Example
This example makes changes to two Cells, first outside, and secondly within, a transaction that is explicitly started and finished. 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.startTransaction();
store.setCell('pets', 'fido', 'color', 'walnut');
store.setCell('pets', 'fido', 'sold', true);
store.finishTransaction();
// -> 'Fido changed'
Since
v1.3.0
transaction
The transaction
method takes a function that makes multiple mutations to the Store
, buffering all calls to the relevant listeners until it completes.
transaction<Return>(
actions: () => Return,
doRollback?: (changedCells: ChangedCells, invalidCells: InvalidCells) => boolean,
): Return
Type | Description | |
---|---|---|
actions | () => Return | The function to be executed as a transaction. |
doRollback? | (changedCells: ChangedCells, invalidCells: InvalidCells) => boolean | An optional callback that should return |
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 'a'
and then, within a transaction, it was changed to 'b'
and then 'c'
, any CellListener
registered for that cell would be called once as if there had been a single change from 'a'
to 'c'
.
Transactions can be nested. Relevant listeners will be called only when the outermost one completes.
The second, optional parameter, doRollback
is a callback that you can use to rollback the transaction if it did not complete to your satisfaction. It is called with changedCells
and invalidCells
parameters, which inform you of the net changes that have been made during the transaction, and any invalid attempts to do so, respectively.
Examples
This example makes changes to two Cells, first outside, and secondly within, a transaction. In the second case, the Row
listener is only called once.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addRowListener('pets', 'fido', () => console.log('Fido changed'));
store.setCell('pets', 'fido', 'color', 'brown');
store.setCell('pets', 'fido', 'sold', false);
// -> 'Fido changed'
// -> 'Fido changed'
store.transaction(() => {
store.setCell('pets', 'fido', 'color', 'walnut');
store.setCell('pets', 'fido', 'sold', true);
});
// -> 'Fido changed'
This example makes multiple changes to one Cell
. The Cell
listener is called once - and with the final value - only if there is a net overall change.
const store = createStore().setTables({pets: {fido: {species: 'dog'}}});
store.addCellListener(
'pets',
'fido',
'color',
(store, tableId, rowId, cellId, newCell) => console.log(newCell),
);
store.transaction(() => {
store.setCell('pets', 'fido', 'color', 'black');
store.setCell('pets', 'fido', 'color', 'brown');
store.setCell('pets', 'fido', 'color', 'walnut');
});
// -> 'walnut'
store.transaction(() => {
store.setCell('pets', 'fido', 'color', 'black');
store.setCell('pets', 'fido', 'color', 'walnut');
});
// -> undefined
// No net change during the transaction, so the listener is not called.
This example makes multiple changes to the Store
, including some attempts to update a Cell
with invalid values. The doRollback
callback receives information about the changes and invalid attempts, and then judges that the transaction should be rolled back to its original state.
const store = createStore().setTables({
pets: {fido: {species: 'dog', color: 'brown'}},
});
store.transaction(
() => {
store.setCell('pets', 'fido', 'color', 'black');
store.setCell('pets', 'fido', 'eyes', ['left', 'right']);
store.setCell('pets', 'fido', 'info', {sold: null});
},
(changedCells, invalidCells) => {
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'black'}}}
console.log(changedCells);
// -> {pets: {fido: {color: ['brown', 'black']}}}
console.log(invalidCells);
// -> {pets: {fido: {eyes: [['left', 'right']], info: [{sold: null}]}}}
return invalidCells['pets'] != null;
},
);
console.log(store.getTables());
// -> {pets: {fido: {species: 'dog', color: 'brown'}}}
Deleter methods
This is the collection of deleter methods within the Store
interface. There are 5 deleter methods in total.
delTables
The delTables
method lets you remove all of the data in a Store
.
delTables(): Store
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 12 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.
SortedRowIdsListener
The SortedRowIdsListener
type describes a function that is used to listen to changes to sorted Row
Ids
in a Table
.
(
store: Store,
tableId: Id,
cellId: Id | undefined,
descending: boolean,
offset: number,
limit: number | undefined,
sortedRowIds: Ids,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
cellId | Id | undefined | |
descending | boolean | Whether the sorting was in descending order. |
offset | number | |
limit | number | undefined | |
sortedRowIds | Ids | |
returns | void | This has no return value. |
A SortedRowIdsListener
is provided when using the addSortedRowIdsListener
method. See that method for specific examples.
When called, a SortedRowIdsListener
is given a reference to the Store
, the Id
of the Table
whose Row
Ids
sorting changed, the Cell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to getSortedRowIds.
Since
v2.0.0
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: CellOrUndefined, newCell: CellOrUndefined]
This is returned by the GetCellChange
function that is provided to every listener when called. This array contains the previous value of a Cell
before the current transaction, the new value after it, and a convenience flag that indicates that the value has changed.
CellListener
The CellListener
type describes a function that is used to listen to changes to a Cell
.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
newCell: Cell,
oldCell: Cell,
getCellChange: GetCellChange | undefined,
): void
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.
InvalidCellListener
The InvalidCellListener
type describes a function that is used to listen to attempts to set invalid data to a Cell
.
(
store: Store,
tableId: Id,
rowId: Id,
cellId: Id,
invalidCells: any[],
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
tableId | Id | |
rowId | Id | |
cellId | Id | |
invalidCells | any[] | An array of the values of the |
returns | void | This has no return value. |
A InvalidCellListener
is provided when using the addInvalidCellListener
method. See that method for specific examples.
When called, a InvalidCellListener
is given a reference to the Store
, the Id
of the Table
, the Id
of the Row
, and the Id
of Cell
that were being attempted to be changed. It is also given the invalid value of the Cell
, which could have been of absolutely any type. Since there could have been multiple failed attempts to set the Cell
within a single transaction, this is an array containing each attempt, chronologically.
Since
v1.1.0
TransactionListener
The TransactionListener
type describes a function that is used to listen to the completion of a transaction for the Store
.
(
store: Store,
cellsTouched: boolean,
): void
Type | Description | |
---|---|---|
store | Store | A reference to the |
cellsTouched | boolean | Whether |
returns | void | This has no return value. |
A TransactionListener
is provided when using the addWillFinishTransactionListener and addDidFinishTransactionListener
methods. See those methods for specific examples.
When called, a TransactionListener
is simply given a reference to the Store
and a boolean to indicate whether Cell
values have been touched during the transaction. The latter flag is intended as a hint about whether non-mutating listeners might be being called at the end of the transaction.
Here, 'touched' means that Cell
values have either been changed, or changed and then changed back to its original value during the transaction. The exception is a transaction that has been rolled back, for which the value of cellsTouched
in the listener will be false
because all changes have been reverted.
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): CellOrUndefined
Type | Description | |
---|---|---|
cellId | Id | |
returns | CellOrUndefined |
A GetCell
can be provided when setting definitions, as in the setMetricDefinition
method of a Metrics
object, or the setIndexDefinition
method of an Indexes
object. See those methods for specific examples.
MapCell
The MapCell
type describes a function that takes an existing Cell
value and returns another.
(cell: CellOrUndefined): Cell
Type | Description | |
---|---|---|
cell | CellOrUndefined | The current value of the |
returns | 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 5 store type aliases in total.
Tables
The Tables
type is the data structure representing all of the data in a Store
.
{[tableId: Id]: Table}
A Tables
object is used when setting all of the tables together with the setTables
method, and when getting them back out again with the getTables
method. A Tables
object is a regular JavaScript object containing individual Table
objects, keyed by their Id
.
Example
const tables: Tables = {
pets: {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
},
species: {
dog: {price: 5},
cat: {price: 4},
},
};
Table
The Table
type is the data structure representing the data in a single table.
{[rowId: Id]: Row}
A Table
is used when setting a table with the setTable
method, and when getting it back out again with the getTable
method. A Table
object is a regular JavaScript object containing individual Row
objects, keyed by their Id
.
Example
const table: Table = {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat'},
};
Row
The Row
type is the data structure representing the data in a single row.
{[cellId: Id]: Cell}
A Row
is used when setting a row with the setRow
method, and when getting it back out again with the getRow
method. A Row
object is a regular JavaScript object containing individual Cell
objects, keyed by their Id
.
Example
const row: Row = {species: 'dog', color: 'brown'};
Cell
The Cell
type is the data structure representing the data in a single cell.
string | number | boolean
A Cell
is used when setting a cell with the setCell
method, and when getting it back out again with the getCell
method. A Cell
is a JavaScript string, number, or boolean.
Example
const cell: Cell = 'dog';
CellOrUndefined
The CellOrUndefined
type is a data structure representing the data in a single cell or the value undefined
.
Cell | undefined
This is used when describing a Cell
that is present or that is not present
- such as when it has been deleted, or when describing a previous state where the
Cell
value has since been added.
Transaction type aliases
This is the collection of transaction type aliases within the store
module. There are only two transaction type aliases, ChangedCells
and InvalidCells
.
ChangedCells
The ChangedCells
type describes the Cell
values that have been changed during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: [CellOrUndefined, CellOrUndefined]}}}
A ChangedCells
object is provided to the doRollback
callback when using the transaction
method. See that method for specific examples.
This type is a nested structure of Table
, Row
, and Cell
objects, much like the Tables
object, but one which provides both the old and new Cell
values in a two-part array. These are describing the state of each changed Cell
in Store
at the start of the transaction, and by the end of the transaction.
Hence, an undefined
value for the first item in the array means that the Cell
was added during the transaction. An undefined
value for the second item in the array means that the Cell
was removed during the transaction. An array with two different Cell
values indicates that it was changed. The two-part array will never contain two items of the same value (including two undefined
values), even if, during the transaction, a Cell
was changed to a different value and then changed back.
Since
v1.2.0
InvalidCells
The InvalidCells
type describes the invalid Cell
values that have been attempted during a transaction, primarily used so that you can indicate whether the transaction should be rolled back.
{[tableId: Id]: {[rowId: Id]: {[cellId: Id]: any[]}}}
An InvalidCells
object is provided to the doRollback
callback when using the transaction
method. See that method for specific examples.
This type is a nested structure of Table
, Row
, and Cell
objects, much like the Tables
object, but one for which Cell
values are listed in array (much like the InvalidCellListener
type) so that multiple failed attempts to change a Cell
during the transaction are described.
Since
v1.2.0
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;
sortedRowIds?: number;
row?: number;
cellIds?: number;
cell?: number;
invalidCell?: number;
transaction?: number;
}
Type | Description | |
---|---|---|
tables? | number | The number of |
tableIds? | number | The number of |
table? | number | The number of |
rowIds? | number | The number of |
sortedRowIds? | number | The number of |
row? | number | The number of |
cellIds? | number | The number of |
cell? | number | The number of |
invalidCell? | number | The number of |
transaction? | number | The number of |
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 Aggregate by providing a shortcut for when a single value is added to the input values - for example, when a |
aggregateRemove? | AggregateRemove | A function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is removed from the input values - for example ,when a |
aggregateReplace? | AggregateReplace | A function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value in the input values is replaced with another - for example, when a |
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 of 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 |
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 SlideIdsListener functions registered with the |
sliceRowIds? | number | The number of |
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
);
console.log(relationships.getRemoteRowId('petSpecies', 'fido'));
// -> 'dog'
console.log(relationships.getLocalRowIds('petSpecies', 'dog'));
// -> ['fido', 'cujo']
// 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.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
Drawing 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 |
localRowIds? | number | The number of |
linkedRowIds? | number | The number of LinkedRowId functions registered with the |
A RelationshipsListenerStats
object is returned from the getListenerStats method, and is only populated in a debug build.
queries
The queries
module of the TinyBase project provides the ability to create and track queries of the data in Store
objects.
The main entry point to using the queries
module is the createQueries
function, which returns a new Queries
object. That object in turn has methods that let you create new query definitions, access their results directly, and register listeners for when those results change.
Since
v2.0.0
Interfaces
There is one interface, Queries
, within the queries
module.
Queries
A Queries
object lets you create and track queries of the data in Store
objects.
This is useful for creating a reactive view of data that is stored in physical tables: selecting columns, joining tables together, filtering rows, aggregating data, sorting it, and so on.
This provides a generalized query concept for Store
data. If you just want to create and track metrics, indexes, or relationships between rows, you may prefer to use the dedicated Metrics
, Indexes
, and Relationships
objects, which have simpler APIs.
Create a Queries
object easily with the createQueries
function. From there, you can add new query definitions (with the setQueryDefinition
method), query the results (with the getResultTable
method, the getResultRow
method, the getResultCell
method, and so on), and add listeners for when they change (with the addResultTableListener
method, the addResultRowListener
method, the addResultCellListener
method, and so on).
Example
This example shows a very simple lifecycle of a Queries
object: from creation, to adding definitions, getting their contents, and then registering and removing listeners for them.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown', ownerId: '1'},
felix: {species: 'cat', color: 'black', ownerId: '2'},
cujo: {species: 'dog', color: 'black', ownerId: '3'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
// A filtered table query:
queries.setQueryDefinition('blackPets', 'pets', ({select, where}) => {
select('species');
where('color', 'black');
});
console.log(queries.getResultTable('blackPets'));
// -> {felix: {species: 'cat'}, cujo: {species: 'dog'}}
// A joined table query:
queries.setQueryDefinition('petOwners', 'pets', ({select, join}) => {
select('owners', 'name').as('owner');
join('owners', 'ownerId');
});
console.log(queries.getResultTable('petOwners'));
// -> {fido: {owner: 'Alice'}, felix: {owner: 'Bob'}, cujo: {owner: 'Carol'}}
// A grouped query:
queries.setQueryDefinition(
'colorPrice',
'pets',
({select, join, group}) => {
select('color');
select('species', 'price');
join('species', 'species');
group('price', 'avg');
},
);
console.log(queries.getResultTable('colorPrice'));
// -> {"1": {color: 'black', price: 4.5}, "0": {color: 'brown', price: 5}}
console.log(queries.getResultSortedRowIds('colorPrice', 'price', true));
// -> ["0", "1"]
const listenerId = queries.addResultTableListener('colorPrice', () => {
console.log('Average prices per color changed');
console.log(queries.getResultTable('colorPrice'));
console.log(queries.getResultSortedRowIds('colorPrice', 'price', true));
});
store.setRow('pets', 'lowly', {species: 'worm', color: 'brown'});
// -> 'Average prices per color changed'
// -> {"0": {color: 'brown', price: 3}, "1": {color: 'black', price: 4.5}}
// -> ["1", "0"]
queries.delListener(listenerId);
queries.destroy();
See also
Making Queries guides
Car Analysis demo
Movie Database demo
Since
v2.0.0
Getter methods
This is the collection of getter methods within the Queries
interface. There are 4 getter methods in total.
getStore
The getStore method returns a reference to the underlying Store
that is backing this Queries
object.
getStore(): Store
returns | Store | A reference to the Store. |
---|
Example
This example creates a Queries
object against a newly-created Store
and then gets its reference in order to update its data.
const queries = createQueries(createStore());
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
queries
.getStore()
.setRow('pets', 'fido', {species: 'dog', color: 'brown'});
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}}
Since
v2.0.0
getTableId
The getTableId method returns the Id
of the underlying Table
that is backing a query.
getTableId(queryId: string): undefined | string
Type | Description | |
---|---|---|
queryId | string | The |
returns | undefined | string | The Id of the Table backing the query, or `undefined`. |
If the query Id
is invalid, the method returns undefined
.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the underlying Table
Id
.
const queries = createQueries(createStore()).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getTableId('dogColors'));
// -> 'pets'
console.log(queries.getTableId('catColors'));
// -> undefined
Since
v2.0.0
getQueryIds
The getQueryIds
method returns an array of the query Ids
registered with this Queries
object.
getQueryIds(): Ids
returns | Ids | An array of Ids. |
---|
Example
This example creates a Queries
object with two definitions, and then gets the Ids
of the definitions.
const queries = createQueries(createStore())
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
console.log(queries.getQueryIds());
// -> ['dogColors', 'catColors']
Since
v2.0.0
hasQuery
The hasQuery
method returns a boolean indicating whether a given query exists in the Queries
object.
hasQuery(queryId: string): boolean
Type | Description | |
---|---|---|
queryId | string | |
returns | boolean | Whether a query with that Id exists. |
Example
This example shows two simple query existence checks.
const queries = createQueries(createStore()).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasQuery('dogColors'));
// -> true
console.log(queries.hasQuery('catColors'));
// -> false
Since
v2.0.0
Result methods
This is the collection of result methods within the Queries
interface. There are 9 result methods in total.
getResultTable
The getResultTable
method returns an object containing the entire data of the result Table
of the given query.
getResultTable(queryId: string): Table
Type | Description | |
---|---|---|
queryId | string | The |
returns | Table | An object containing the entire data of the result Table of the query. |
This has the same behavior as a Store
's getTable
method. For example, if the query Id
is invalid, the method returns an empty object. Similarly, it returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the result Table
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}, cujo: {color: 'black'}}
console.log(queries.getResultTable('catColors'));
// -> {}
Since
v2.0.0
hasResultTable
The hasResultTable
method returns a boolean indicating whether a given result Table
exists.
hasResultTable(queryId: string): boolean
Type | Description | |
---|---|---|
queryId | string | The |
returns | boolean | Whether a result Table for that query Id exists. |
Example
This example shows two simple result Table
existence checks.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultTable('dogColors'));
// -> true
console.log(queries.hasResultTable('catColors'));
// -> false
Since
v2.0.0
getResultRowIds
The getResultRowIds
method returns the Ids
of every Row
in the result Table
of the given query.
getResultRowIds(queryId: string): Ids
Type | Description | |
---|---|---|
queryId | string | The |
returns | Ids | An array of the Ids of every Row in the result of the query. |
This has the same behavior as a Store
's getRowIds
method. For example, if the query Id
is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids
, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the result Row
Ids
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRowIds('dogColors'));
// -> ['fido', 'cujo']
console.log(queries.getResultRowIds('catColors'));
// -> []
Since
v2.0.0
getResultSortedRowIds
The getResultSortedRowIds
method returns the Ids
of every Row
in the result Table
of the given query, sorted according to the values in a specified Cell
.
getResultSortedRowIds(
queryId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
returns | Ids | An array of the sorted Ids of every Row in the result of the query. |
This has the same behavior as a Store
's getSortedRowIds
method. For example, if the query Id
is invalid, the method returns an empty array. Similarly, the sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
Note that every call to this method will perform the sorting afresh - there is no caching of the results - and so you are advised to memoize the results yourself, especially when the result Table
is large. For a performant approach to tracking the sorted Row
Ids
when they change, use the addResultSortedRowIdsListener
method.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent definition) to get the result Row
Ids
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultSortedRowIds('dogColors', 'color'));
// -> ['cujo', 'fido']
console.log(queries.getResultSortedRowIds('catColors', 'color'));
// -> []
Since
v2.0.0
getResultRow
The getResultRow
method returns an object containing the entire data of a single Row
in the result Table
of the given query.
getResultRow(
queryId: string,
rowId: string,
): Row
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
returns | Row | An object containing the entire data of the Row in the result Table of the query. |
This has the same behavior as a Store
's getRow
method. For example, if the query or Row
Id
is invalid, the method returns an empty object. Similarly, it returns a copy of, rather than a reference to the underlying data, so changes made to the returned object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent Row
Id
) to get the result Row
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultRow('dogColors', 'fido'));
// -> {color: 'brown'}
console.log(queries.getResultRow('dogColors', 'felix'));
// -> {}
Since
v2.0.0
hasResultRow
The hasResultRow
method returns a boolean indicating whether a given result Row
exists.
hasResultRow(
queryId: string,
rowId: string,
): boolean
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
returns | boolean | Whether a result Row for that Id exists. |
Example
This example shows two simple result Row
existence checks.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultRow('dogColors', 'fido'));
// -> true
console.log(queries.hasResultRow('dogColors', 'felix'));
// -> false
Since
v2.0.0
getResultCellIds
The getResultCellIds
method returns the Ids
of every Cell
in a given Row
, in the result Table
of the given query.
getResultCellIds(
queryId: string,
rowId: string,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
returns | Ids | An array of the Ids of every Cell in the Row in the result of the query. |
This has the same behavior as a Store
's getCellIds
method. For example, if the query Id
or Row
Id
is invalid, the method returns an empty array. Similarly, it returns a copy of, rather than a reference to the list of Ids
, so changes made to the list object are not made to the query results themselves.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent Row
Id
) to get the result Cell
Ids
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultCellIds('dogColors', 'fido'));
// -> ['color']
console.log(queries.getResultCellIds('dogColors', 'felix'));
// -> []
Since
v2.0.0
getResultCell
The getResultCell
method returns the value of a single Cell
in a given Row
, in the result Table
of the given query.
getResultCell(
queryId: string,
rowId: string,
cellId: string,
): CellOrUndefined
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
cellId | string | |
returns | CellOrUndefined | The value of the Cell, or `undefined`. |
This has the same behavior as a Store
's getCell
method. For example, if the query, or Row
, or Cell
Id
is invalid, the method returns undefined
.
Example
This example creates a Queries
object, a single query definition, and then calls this method on it (as well as a non-existent Cell
Id
) to get the result Cell
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultCell('dogColors', 'fido', 'color'));
// -> 'brown'
console.log(queries.getResultCell('dogColors', 'fido', 'species'));
// -> undefined
Since
v2.0.0
hasResultCell
The hasResultCell
method returns a boolean indicating whether a given result Cell
exists.
hasResultCell(
queryId: string,
rowId: string,
cellId: string,
): boolean
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
cellId | string | |
returns | boolean | Whether a result Cell for that Id exists. |
Example
This example shows two simple result Row
existence checks.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.hasResultCell('dogColors', 'fido', 'color'));
// -> true
console.log(queries.hasResultCell('dogColors', 'fido', 'species'));
// -> false
Since
v2.0.0
Listener methods
This is the collection of listener methods within the Queries
interface. There are 7 listener methods in total.
addResultTableListener
The addResultTableListener
method registers a listener function with the Queries
object that will be called whenever data in a result Table
changes.
addResultTableListener(
queryId: IdOrNull,
listener: ResultTableListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultTableListener | The function that will be called whenever data in the matching result |
returns | string | A unique Id for the listener that can later be used to remove it. |
The provided listener is a ResultTableListener
function, and will be called with a reference to the Queries
object, the Id
of the Table
that changed (which is also the query Id
), and a GetCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single result Table
(by specifying a query Id
as the method's first parameter) or changes to any result Table
(by providing a null
wildcard).
Examples
This example registers a listener that responds to any changes to a specific result Table
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultTableListener(
'dogColors',
(queries, tableId, getCellChange) => {
console.log('dogColors result table changed');
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'dogColors result table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any result Table
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultTableListener(
null,
(queries, tableId) => {
console.log(`${tableId} result table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'dogColors result table changed'
store.setCell('pets', 'felix', 'color', 'tortoiseshell');
// -> 'catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultRowIdsListener
The addResultRowIdsListener
method registers a listener function with the Queries
object that will be called whenever the Row
Ids
in a result Table
change.
addResultRowIdsListener(
queryId: IdOrNull,
listener: ResultRowIdsListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultRowIdsListener | The function that will be called whenever the |
returns | string | A unique Id for the listener that can later be used to remove it. |
The provided listener is a ResultRowIdsListener
function, and will be called with a reference to the Queries
object and the Id
of the Table
that changed (which is also the query Id
).
By default, such a listener is only called when a Row
is added to, or removed from, the result Table
. To listen to all changes in the result Table
, use the addResultTableListener
method.
You can either listen to a single result Table
(by specifying a query Id
as the method's first parameter) or changes to any result Table
(by providing a null
wildcard).
Examples
This example registers a listener that responds to any change to the Row
Ids
of a specific result Table
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowIdsListener(
'dogColors',
(queries, tableId) => {
console.log(`Row Ids for dogColors result table changed`);
console.log(queries.getResultRowIds('dogColors'));
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row Ids for dogColors result table changed'
// -> ['fido', 'cujo', 'rex']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Row
Ids
of any result Table
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowIdsListener(
null,
(queries, tableId) => {
console.log(`Row Ids for ${tableId} result table changed`);
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Row Ids for dogColors result table changed'
store.setRow('pets', 'tom', {species: 'cat', color: 'gray'});
// -> 'Row Ids for catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultSortedRowIdsListener
The addResultSortedRowIdsListener
method registers a listener function with the Queries
object that will be called whenever sorted (and optionally, paginated) Row
Ids
in a result Table
change.
addResultSortedRowIdsListener(
queryId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: ResultSortedRowIdsListener,
): string
Type | Description | |
---|---|---|
queryId | string | The |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | ResultSortedRowIdsListener | The function that will be called whenever the sorted |
returns | string | A unique Id for the listener that can later be used to remove it. |
The provided listener is a ResultSortedRowIdsListener
function, and will be called with a reference to the Queries
object, the Id
of the result Table
whose Row
Ids
sorting changed (which is also the query Id
), the Cell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to getResultSortedRowIds
Such a listener is called when a Row
is added or removed, but also when a value in the specified Cell
(somewhere in the result Table
) has changed enough to change the sorting of the Row
Ids
.
Unlike most other listeners, you cannot provide wildcards (due to the cost of detecting changes to the sorting). You can only listen to a single specified result Table
, sorted by a single specified Cell
.
The sorting of the rows is alphanumeric, and you can indicate whether it should be in descending order. The offset
and limit
parameters are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
Examples
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific result Table
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultSortedRowIdsListener(
'dogColors',
'color',
false,
0,
undefined,
(queries, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for dogColors result table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getResultSortedRowIds again
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Sorted Row Ids for dogColors result table changed'
// -> ['cujo', 'fido', 'rex']
store.delListener(listenerId);
This example registers a listener that responds to any change to the sorted Row
Ids
of a specific Table
. The Row
Ids
are sorted by their own value, since the cellId
parameter is explicitly undefined.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
console.log(queries.getResultSortedRowIds('dogColors', undefined));
// -> ['cujo', 'fido']
const listenerId = queries.addResultSortedRowIdsListener(
'dogColors',
undefined,
false,
0,
undefined,
(queries, tableId, cellId, descending, offset, limit, sortedRowIds) => {
console.log(`Sorted Row Ids for dogColors result table changed`);
console.log(sortedRowIds);
// ^ cheaper than calling getSortedRowIds again
},
);
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Sorted Row Ids for dogColors result table changed'
// -> ['cujo', 'fido', 'rex']
store.delListener(listenerId);
Since
v2.0.0
addResultRowListener
The addResultRowListener
method registers a listener function with the Queries
object that will be called whenever data in a result Row
changes.
addResultRowListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultRowListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultRowListener | The function that will be called whenever data in the matching result |
returns | string | A unique Id for the listener that can later be used to remove it. |
The provided listener is a ResultRowListener
function, and will be called with a reference to the Queries
object, the Id
of the Table
that changed (which is also the query Id
), and a GetCellChange
function in case you need to inspect any changes that occurred.
You can either listen to a single result Row
(by specifying a query Id
and Row
Id
as the method's first two parameters) or changes to any result Row
(by providing null
wildcards).
Both, either, or neither of the queryId
and rowId
parameters can be wildcarded with null
. You can listen to a specific result Row
in a specific query, any result Row
in a specific query, a specific result Row
in any query, or any result Row
in any query.
Examples
This example registers a listener that responds to any changes to a specific result Row
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultRowListener(
'dogColors',
'fido',
(queries, tableId, rowId, getCellChange) => {
console.log('fido row in dogColors result table changed');
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in dogColors result table changed'
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any result Row
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
const listenerId = queries.addResultRowListener(
null,
null,
(queries, tableId, rowId) => {
console.log(`${rowId} row in ${tableId} result table changed`);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'color', 'tortoiseshell');
// -> 'felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultCellIdsListener
The addResultCellIdsListener
method registers a listener function with the Queries
object that will be called whenever the Cell
Ids
in a result Row
change.
addResultCellIdsListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultCellIdsListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultCellIdsListener | The function that will be called whenever the |
returns | string | A unique Id for the listener that can later be used to remove it. |
The provided listener is a ResultCellIdsListener
function, and will be called with a reference to the Queries
object, the Id
of the Table
(which is also the query Id
), and the Id
of the result Row
that changed.
Such a listener is only called when a Cell
is added to, or removed from, the result Row
. To listen to all changes in the result Row
, use the addResultRowListener
method.
You can either listen to a single result Row
(by specifying the query 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 queryId
and rowId
parameters can be wildcarded with null
. You can listen to a specific result Row
in a specific query, any result Row
in a specific query, a specific result Row
in any query, or any result Row
in any query.
Examples
This example registers a listener that responds to any change to the Cell
Ids
of a specific result Row
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
select('price');
where('species', 'dog');
},
);
const listenerId = queries.addResultCellIdsListener(
'dogColors',
'fido',
(store, tableId, rowId) => {
console.log(`Cell Ids for fido row in dogColors result table changed`);
console.log(queries.getResultCellIds('dogColors', 'fido'));
},
);
store.setCell('pets', 'fido', 'price', 5);
// -> 'Cell Ids for fido row in dogColors result table changed'
// -> ['color', 'price']
store.delListener(listenerId);
This example registers a listener that responds to any change to the Cell
Ids
of any result Row
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
select('price');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
select('purrs');
where('species', 'cat');
});
const listenerId = queries.addResultCellIdsListener(
null,
null,
(queries, tableId, rowId) => {
console.log(
`Cell Ids for ${rowId} row in ${tableId} result table changed`,
);
},
);
store.setCell('pets', 'fido', 'price', 5);
// -> 'Cell Ids for fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'purrs', true);
// -> 'Cell Ids for felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
addResultCellListener
The addResultCellListener
method registers a listener function with the Queries
object that will be called whenever data in a result Cell
changes.
addResultCellListener(
queryId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: ResultCellListener,
): string
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
cellId | IdOrNull | The |
listener | ResultCellListener | The function that will be called whenever data in the matching result |
returns | string | A unique Id for the listener that can later be used to remove it. |
The provided listener is a ResultCellListener
function, and will be called with a reference to the Queries
object, the Id
of the Table
that changed (which is also the query Id
), 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.
You can either listen to a single result Row
(by specifying a query Id
, Row
Id
, and Cell
Id
as the method's first three parameters) or changes to any result Cell
(by providing null
wildcards).
All, some, or none of the queryId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific result Row
in a specific query, any Cell
in any result Row
in any query, for example - or every other combination of wildcards.
Examples
This example registers a listener that responds to any changes to a specific result Cell
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const listenerId = queries.addResultCellListener(
'dogColors',
'fido',
'color',
(queries, tableId, rowId, cellId, newCell, oldCell, getCellChange) => {
console.log(
'color cell in fido row in dogColors result table changed',
);
console.log([oldCell, newCell]);
console.log(getCellChange('dogColors', 'fido', 'color'));
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in dogColors result table changed'
// -> ['brown', 'walnut']
// -> [true, 'brown', 'walnut']
store.delListener(listenerId);
This example registers a listener that responds to any changes to any result Cell
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown', price: 5},
felix: {species: 'cat', color: 'black', price: 4},
cujo: {species: 'dog', color: 'black', price: 5},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
select('price');
where('species', 'cat');
});
const listenerId = queries.addResultCellListener(
null,
null,
null,
(queries, tableId, rowId, cellId) => {
console.log(
`${cellId} cell in ${rowId} row in ${tableId} result table changed`,
);
},
);
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'color cell in fido row in dogColors result table changed'
store.setCell('pets', 'felix', 'price', 3);
// -> 'price cell in felix row in catColors result table changed'
store.delListener(listenerId);
Since
v2.0.0
delListener
The delListener method removes a listener that was previously added to the Queries
object.
delListener(listenerId: string): Queries
Type | Description | |
---|---|---|
listenerId | string | The |
returns | Queries | A reference to the Queries object. |
Use the Id
returned by the addMetricListener
method. Note that the Queries
object may re-use this Id
for future listeners added to it.
Example
This example creates a Store
, a Queries
object, registers a listener, and then removes it.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store).setQueryDefinition(
'species',
'pets',
({select}) => {
select('species');
},
);
const listenerId = queries.addResultTableListener('species', (queries) =>
console.log('species result changed'),
);
store.setCell('pets', 'ed', 'species', 'horse');
// -> 'species result changed'
queries.delListener(listenerId);
store.setCell('pets', 'molly', 'species', 'cow');
// -> undefined
// The listener is not called.
Since
v2.0.0
Configuration methods
This is the collection of configuration methods within the Queries
interface. There are only two configuration methods, delQueryDefinition
and setQueryDefinition
.
delQueryDefinition
The delQueryDefinition
method removes an existing query definition.
delQueryDefinition(queryId: string): Queries
Type | Description | |
---|---|---|
queryId | string | The |
returns | Queries | A reference to the Queries object. |
Example
This example creates a Store
, creates a Queries
object, defines a simple query, and then removes it.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
console.log(queries.getQueryIds());
// -> ['dogColors']
queries.delQueryDefinition('dogColors');
console.log(queries.getQueryIds());
// -> []
Since
v2.0.0
setQueryDefinition
The setQueryDefinition
method lets you set the definition of a query.
setQueryDefinition(
queryId: string,
tableId: string,
query: (keywords: {
select: Select;
join: Join;
where: Where;
group: Group;
having: Having;
}) => void,
): Queries
Type | Description | |
---|---|---|
queryId | string | The |
tableId | string | |
query | (keywords: { select: Select; join: Join; where: Where; group: Group; having: Having; }) => void | A callback which can take a |
returns | Queries | A reference to the Queries object. |
Every query definition is identified by a unique Id
, and if you re-use an existing Id
with this method, the previous definition is overwritten.
A query provides a tabular result formed from each Row
within a main Table
. The definition must specify this 'main' Table
(by its Id
) to be aggregated. Other Tables
can be joined to that using Join
clauses.
The third query
parameter is a callback that you provide to define the query. That callback is provided with a keywords
object that contains the functions you use to define the query, like select
, join
, and so on. You can see how that is used in the simple example below. The following five clause types are supported:
- The
Select
type describes a function that lets you specify aCell
or calculated value for including into the query's result. - The
Join
type describes a function that lets you specify aCell
or calculated value to join the main queryTable
to others, byRow
Id
. - The
Where
type describes a function that lets you specify conditions to filter results, based on the underlying Cells of the main or joinedTables
. - The
Group
type describes a function that lets you specify that the values of aCell
in multiple result Rows should be aggregated together. - The
Having
type describes a function that lets you specify conditions to filter results, based on the grouped Cells resulting from aGroup
clause.
Full documentation and examples are provided in the sections for each of those clause types.
Additionally, you can use the getResultSortedRowIds
method and addResultSortedRowIdsListener
method to sort and paginate the results.
Example
This example creates a Store
, creates a Queries
object, and defines a simple query to select just one column from the Table
, for each Row
where the species
Cell
matches as certain value.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store);
queries.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
console.log(queries.getResultTable('dogColors'));
// -> {fido: {color: 'brown'}, cujo: {color: 'black'}}
Since
v2.0.0
Iterator methods
This is the collection of iterator methods within the Queries
interface. There are 4 iterator methods in total.
forEachResultTable
The forEachResultTable
method takes a function that it will then call for each result Table
in the Queries
object.
forEachResultTable(tableCallback: TableCallback): void
Type | Description | |
---|---|---|
tableCallback | TableCallback | The function that should be called for every query's result |
returns | void | This has no return value. |
This method is useful for iterating over all the result Tables
of the queries in a functional style. The tableCallback
parameter is a TableCallback
function that will be called with the Id
of each result Table
, and with a function that can then be used to iterate over each Row
of the result Table
, should you wish.
Example
This example iterates over each query's result Table
in a Queries
object.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store)
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
queries.forEachResultTable((queryId, forEachRow) => {
console.log(queryId);
forEachRow((rowId) => console.log(`- ${rowId}`));
});
// -> 'dogColors'
// -> '- fido'
// -> '- cujo'
// -> 'catColors'
// -> '- felix'
Since
v2.0.0
forEachResultRow
The forEachResultRow
method takes a function that it will then call for each Row
in the result Table
of a query.
forEachResultRow(
queryId: string,
rowCallback: RowCallback,
): void
Type | Description | |
---|---|---|
queryId | string | The |
rowCallback | RowCallback | The function that should be called for every |
returns | void | This has no return value. |
This method is useful for iterating over each Row
of the result Table
of the query in a functional style. The rowCallback
parameter is a RowCallback
function that will be called with the Id
of each result Row
, and with a function that can then be used to iterate over each Cell
of the result Row
, should you wish.
Example
This example iterates over each Row
in a query's result Table
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
queries.forEachResultRow('dogColors', (rowId, forEachCell) => {
console.log(rowId);
forEachCell((cellId) => console.log(`- ${cellId}`));
});
// -> 'fido'
// -> '- color'
// -> 'cujo'
// -> '- color'
Since
v2.0.0
forEachResultCell
The forEachResultCell
method takes a function that it will then call for each Cell
in the result Row
of a query.
forEachResultCell(
queryId: string,
rowId: string,
cellCallback: CellCallback,
): void
Type | Description | |
---|---|---|
queryId | string | The |
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 each Cell
of the result Row
of the query in a functional style. The cellCallback
parameter is a CellCallback
function that will be called with the Id
and value of each result Cell
.
Example
This example iterates over each Cell
in a query's result Row
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
where('species', 'dog');
},
);
queries.forEachResultCell('dogColors', 'fido', (cellId, cell) => {
console.log(`${cellId}: ${cell}`);
});
// -> 'species: dog'
// -> 'color: brown'
Since
v2.0.0
forEachQuery
The forEachQuery
method takes a function that it will then call for each Query in the Queries
object.
forEachQuery(queryCallback: QueryCallback): void
Type | Description | |
---|---|---|
queryCallback | QueryCallback | The function that should be called for every query. |
returns | void | This has no return value. |
This method is useful for iterating over all the queries in a functional style. The queryCallback
parameter is a QueryCallback
function that will be called with the Id
of each query.
Example
This example iterates over each query in a Queries
object.
const queries = createQueries(createStore())
.setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
})
.setQueryDefinition('catColors', 'pets', ({select, where}) => {
select('color');
where('species', 'cat');
});
queries.forEachQuery((queryId) => {
console.log(queryId);
});
// -> 'dogColors'
// -> 'catColors'
Since
v2.0.0
Lifecycle methods
This is the collection of lifecycle methods within the Queries
interface. There is only one method, destroy
.
destroy
The destroy method should be called when this Queries
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 Queries
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 queries = createQueries(store);
queries.setQueryDefinition('species', 'species', ({select}) => {
select('species');
});
console.log(store.getListenerStats().row);
// -> 1
queries.destroy();
console.log(store.getListenerStats().row);
// -> 0
Since
v2.0.0
Development methods
This is the collection of development methods within the Queries
interface. There is only one method, getListenerStats
.
getListenerStats
The getListenerStats method provides a set of statistics about the listeners registered with the Queries
object, and is used for debugging purposes.
getListenerStats(): QueriesListenerStats
returns | QueriesListenerStats | A QueriesListenerStats object containing Queries 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 Queries
object.
const store = createStore();
const queries = createQueries(store);
queries.addResultTableListener(null, () => console.log('Result changed'));
console.log(queries.getListenerStats().table);
// -> 1
console.log(queries.getListenerStats().row);
// -> 0
Since
v2.0.0
Functions
There is one function, createQueries
, within the queries
module.
createQueries
The createQueries
function creates a Queries
object, and is the main entry point into the queries
module.
createQueries(store: Store): Queries
Type | Description | |
---|---|---|
store | Store | The |
returns | Queries | A reference to the new Queries object. |
It is trivially simple.
A given Store
can only have one Queries
object associated with it. If you call this function twice on the same Store
, your second call will return a reference to the Queries
object created by the first.
Examples
This example creates a Queries
object.
const store = createStore();
const queries = createQueries(store);
console.log(queries.getQueryIds());
// -> []
This example creates a Queries
object, and calls the method a second time for the same Store
to return the same object.
const store = createStore();
const queries1 = createQueries(store);
const queries2 = createQueries(store);
console.log(queries1 === queries2);
// -> true
Since
v2.0.0
Type Aliases
These are the type aliases within the queries
module.
Definition type aliases
This is the collection of definition type aliases within the queries
module. There are 8 definition type aliases in total.
Group
The Group
type describes a function that lets you specify that the values of a Cell
in multiple result Rows should be aggregated together.
(
selectedCellId: Id,
aggregate: "count" | "sum" | "avg" | "min" | "max" | Aggregate,
aggregateAdd?: AggregateAdd,
aggregateRemove?: AggregateRemove,
aggregateReplace?: AggregateReplace,
): GroupedAs
Type | Description | |
---|---|---|
selectedCellId | Id | The |
aggregate | "count" | "sum" | "avg" | "min" | "max" | Aggregate | Either a string representing one of a set of common aggregation techniques ('count', 'sum', 'avg', 'min', or 'max'), or a function that aggregates |
aggregateAdd? | AggregateAdd | A function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is added to the input values - for example, when a |
aggregateRemove? | AggregateRemove | A function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value is removed from the input values - for example ,when a |
aggregateReplace? | AggregateReplace | A function that can be used to optimize a custom Aggregate by providing a shortcut for when a single value in the input values is replaced with another - for example, when a |
returns | GroupedAs | A GroupedAs object so that the grouped Cell Id can be optionally aliased. |
The Group
function is provided to the third query
parameter of the setQueryDefinition
method. When called, it should refer to a Cell
Id
(or aliased Id
) specified in one of the Select
functions, and indicate how the values should be aggregated.
This is applied after any joins or where-based filtering.
If you provide a Group
for every Select
, the result will be a single Row
with every Cell
having been aggregated. If you provide a Group
for only one, or some, of the Select
clauses, the others will be automatically used as dimensional values (analogous to the 'group bysemantics in SQL), within which the aggregations of
Group` Cells will be performed.
You can join the same underlying Cell
multiple times, but in that case you will need to use the 'as' function to distinguish them from each other.
The second parameter can be one of five predefined aggregates - 'count', 'sum', 'avg', 'min', and 'max' - or a custom function that produces your own aggregation of an array of Cell
values.
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 shows a query that calculates the average of all the values in a single selected Cell
from a joined Table
.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
lowly: {species: 'worm'},
})
.setTable('species', {
dog: {price: 5},
cat: {price: 4},
worm: {price: 1},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('species', 'price');
// from pets
join('species', 'species');
group('price', 'avg').as('avgPrice');
});
console.log(queries.getResultTable('query'));
// -> {0: {avgPrice: 3.75}}
// 2 dogs at 5, 1 cat at 4, 1 worm at 1: a total of 15 for 4 pets
This example shows a query that calculates the average of a two Cell
values, aggregated by the two other dimensional 'group by' Cells.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown', owner: 'alice'},
felix: {species: 'cat', color: 'black', owner: 'bob'},
cujo: {species: 'dog', color: 'black', owner: 'bob'},
lowly: {species: 'worm', color: 'brown', owner: 'alice'},
carnaby: {species: 'parrot', color: 'black', owner: 'bob'},
polly: {species: 'parrot', color: 'red', owner: 'alice'},
})
.setTable('species', {
dog: {price: 5, legs: 4},
cat: {price: 4, legs: 4},
parrot: {price: 3, legs: 2},
worm: {price: 1, legs: 0},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('pets', 'color'); // group by
select('pets', 'owner'); // group by
select('species', 'price'); // grouped
select('species', 'legs'); // grouped
// from pets
join('species', 'species');
group('price', 'avg').as('avgPrice');
group('legs', 'sum').as('sumLegs');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {color: 'brown', owner: 'alice', avgPrice: 3, sumLegs: 4}}
// -> {1: {color: 'black', owner: 'bob', avgPrice: 4, sumLegs: 10}}
// -> {2: {color: 'red', owner: 'alice', avgPrice: 3, sumLegs: 2}}
This example shows a query that calculates the a custom aggregate of one Cell
's values, grouped by another. Note how aggregateAdd
, aggregateRemove
, and aggregateReplace
parameters are provided to make the custom aggregation more efficient as individual values are added, removed, or replaced during the lifecycle of the Table
.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', owner: 'alice'},
felix: {species: 'cat', owner: 'bob'},
cujo: {species: 'dog', owner: 'bob'},
lowly: {species: 'worm', owner: 'alice'},
carnaby: {species: 'parrot', owner: 'bob'},
polly: {species: 'parrot', owner: 'alice'},
})
.setTable('species', {
dog: {price: 5, legs: 4},
cat: {price: 4, legs: 4},
parrot: {price: 3, legs: 2},
worm: {price: 1, legs: 0},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, group}) => {
select('pets', 'owner'); // group by
select('species', 'price'); // grouped
// from pets
join('species', 'species');
group(
'price',
(cells) => Math.min(...cells.filter((cell) => cell > 2)),
(current, add) => (add > 2 ? Math.min(current, add) : current),
(current, remove) => (remove == current ? undefined : current),
(current, add, remove) =>
remove == current
? undefined
: add > 2
? Math.min(current, add)
: current,
).as('lowestPriceOver2');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {owner: 'alice', lowestPriceOver2: 3}}
// -> {1: {owner: 'bob', lowestPriceOver2: 3}}
// Both have a parrot at 3. Alice's worm at 1 is excluded from aggregation.
Since
v2.0.0
GroupedAs
The GroupedAs
type describes an object returned from calling a Group
function so that the grouped Cell
Id
can be optionally aliased.
{as: (groupedCellId: string): void}
Type | Description | |
---|---|---|
as | as(groupedCellId: string): void |
Note that if two Group
clauses are both aliased to the same name (or if you create two groups of the same underlying Cell
, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that groups the same underlying Cell
twice, for different purposes. Both groups are aliased with the 'as' function to disambiguate them.
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'dog', minPrice: 4, maxPrice: 5}}
// -> {1: {species: 'cat', minPrice: 3, maxPrice: 4}}
Since
v2.0.0
Having
The Having
type describes a function that lets you specify conditions to filter results, based on the grouped Cells resulting from a Group
clause.
Calling this function with two parameters is used to include only those Rows for which a specified Cell
in the query's main Table
has a specified value.
(
selectedOrGroupedCellId: string,
equals: Cell,
): void
Type | Description | |
---|---|---|
selectedOrGroupedCellId | string | |
equals | Cell | The value that the |
returns | void | This has no return value. |
Calling this function with one callback parameter is used to include only those Rows which meet a calculated boolean condition.
(condition: (getSelectedOrGroupedCell: GetCell) => boolean): void
Type | Description | |
---|---|---|
condition | (getSelectedOrGroupedCell: GetCell) => boolean | A callback that takes a |
returns | void | This has no return value. |
The Having
function is provided to the third query
parameter of the setQueryDefinition
method.
A Having
condition has to be true for a Row
to be included in the results. Each Having
class is additive, as though combined with a logical 'and'. If you wish to create an 'or' expression, use the single parameter version of the type that allows arbitrary programmatic conditions.
The Where
keyword differs from the Having
keyword in that the former describes conditions that should be met by underlying Cell
values (whether selected or not), and the latter describes conditions based on calculated and aggregated values - after Group
clauses have been applied.
Whilst it is technically possible to use a Having
clause even if the results have not been grouped with a Group
clause, you should expect it to be less performant than using a Where
clause, due to that being applied earlier in the query process.
Examples
This example shows a query that filters the results from a grouped Table
by comparing a Cell
from it with a value.
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
carnaby: {species: 'parrot', price: 3},
polly: {species: 'parrot', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group, having}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
having('minPrice', 3);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'cat', minPrice: 3, maxPrice: 4}}
// -> {1: {species: 'parrot', minPrice: 3, maxPrice: 3}}
This example shows a query that filters the results from a grouped Table
with a condition that is calculated from Cell
values.
const store = createStore().setTable('pets', {
fido: {species: 'dog', price: 5},
felix: {species: 'cat', price: 4},
cujo: {species: 'dog', price: 4},
tom: {species: 'cat', price: 3},
carnaby: {species: 'parrot', price: 3},
polly: {species: 'parrot', price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, group, having}) => {
select('pets', 'species');
select('pets', 'price');
group('price', 'min').as('minPrice');
group('price', 'max').as('maxPrice');
having(
(getSelectedOrGroupedCell) =>
getSelectedOrGroupedCell('minPrice') !=
getSelectedOrGroupedCell('maxPrice'),
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {0: {species: 'dog', minPrice: 4, maxPrice: 5}}
// -> {1: {species: 'cat', minPrice: 3, maxPrice: 4}}
// Parrots are filtered out because they have zero range in price.
Since
v2.0.0
Join
The Join
type describes a function that lets you specify a Cell
or calculated value to join the main query Table
to other Tables
, by their Row
Id
.
Calling this function with two Id
parameters will indicate that the join to a Row
in an adjacent Table
is made by finding its Id
in a Cell
of the query's main Table
.
(
joinedTableId: string,
on: string,
): JoinedAs
Type | Description | |
---|---|---|
joinedTableId | string | |
on | string | The |
returns | JoinedAs | A JoinedAs object so that the joined Table Id can be optionally aliased. |
Calling this function with two parameters (where the second is a function) will indicate that the join to a Row
in an adjacent Table
is made by calculating its Id
from the Cells and the Row
Id
of the query's main Table
.
(
joinedTableId: string,
on: (getCell: GetCell, rowId: string) => undefined | string,
): JoinedAs
Type | Description | |
---|---|---|
joinedTableId | string | |
on | (getCell: GetCell, rowId: string) => undefined | string | A callback that takes a |
returns | JoinedAs | A JoinedAs object so that the joined Table Id can be optionally aliased. |
Calling this function with three Id
parameters will indicate that the join to a Row
in distant Table
is made by finding its Id
in a Cell
of an intermediately joined Table
.
(
joinedTableId: string,
fromIntermediateJoinedTableId: string,
on: string,
): JoinedAs
Type | Description | |
---|---|---|
joinedTableId | string | |
fromIntermediateJoinedTableId | string | The |
on | string | The |
returns | JoinedAs | A JoinedAs object so that the joined Table Id can be optionally aliased. |
Calling this function with three parameters (where the third is a function) will indicate that the join to a Row
in distant Table
is made by calculating its Id
from the Cells and the Row
Id
of an intermediately joined Table
.
(
joinedTableId: string,
fromIntermediateJoinedTableId: string,
on: (getIntermediateJoinedCell: GetCell, intermediateJoinedTableRowId: string) => undefined | string,
): JoinedAs
Type | Description | |
---|---|---|
joinedTableId | string | |
fromIntermediateJoinedTableId | string | The |
on | (getIntermediateJoinedCell: GetCell, intermediateJoinedTableRowId: string) => undefined | string | A callback that takes a |
returns | JoinedAs | A JoinedAs object so that the joined Table Id can be optionally aliased. |
The Join
function is provided to the third query
parameter of the setQueryDefinition
method.
You can join zero, one, or many Tables
. You can join the same underlying Table
multiple times, but in that case you will need to use the 'as' function to distinguish them from each other.
By default, each join is made from the main query Table
to the joined table, but it is also possible to connect via an intermediate join Table
to a more distant join Table
.
Because a Join
clause is used to identify which unique Row
Id
of the joined Table
will be joined to each Row
of the main Table
, queries follow the 'left join' semantics you may be familiar with from SQL. This means that an unfiltered query will only ever return the same number of Rows as the main Table
being queried, and indeed the resulting table (assuming it has not been aggregated) will even preserve the main Table
's original Row
Ids
.
Examples
This example shows a query that joins a single Table
by using an Id
present in the main query Table
.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species');
select('owners', 'name');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', name: 'Alice'}}
// -> {felix: {species: 'cat', name: 'Bob'}}
// -> {cujo: {species: 'dog', name: 'Carol'}}
This example shows a query that joins the same underlying Table
twice, and aliases them (and the selected Cell
Ids
). Note the left-join semantics: Felix the cat was bought, but the seller was unknown. The record still exists in the result Table
.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', buyerId: '1', sellerId: '2'},
felix: {species: 'cat', buyerId: '2'},
cujo: {species: 'dog', buyerId: '3', sellerId: '1'},
})
.setTable('humans', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('buyers', 'name').as('buyer');
select('sellers', 'name').as('seller');
// from pets
join('humans', 'buyerId').as('buyers');
join('humans', 'sellerId').as('sellers');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {buyer: 'Alice', seller: 'Bob'}}
// -> {felix: {buyer: 'Bob'}}
// -> {cujo: {buyer: 'Carol', seller: 'Alice'}}
This example shows a query that calculates the Id
of the joined Table
based from multiple values in the main Table
rather than a single Cell
.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
})
.setTable('colorSpecies', {
'brown-dog': {price: 6},
'black-dog': {price: 5},
'brown-cat': {price: 4},
'black-cat': {price: 3},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('colorSpecies', 'price');
// from pets
join(
'colorSpecies',
(getCell) => `${getCell('color')}-${getCell('species')}`,
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {price: 6}}
// -> {felix: {price: 3}}
// -> {cujo: {price: 5}}
This example shows a query that joins two Tables
, one through the intermediate other.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
})
.setTable('states', {
CA: {name: 'California'},
WA: {name: 'Washington'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select(
(getTableCell, rowId) =>
`${getTableCell('species')} in ${getTableCell('states', 'name')}`,
).as('description');
// from pets
join('owners', 'ownerId');
join('states', 'owners', 'state');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {description: 'dog in California'}}
// -> {felix: {description: 'cat in California'}}
// -> {cujo: {description: 'dog in Washington'}}
Since
v2.0.0
JoinedAs
The JoinedAs
type describes an object returned from calling a Join
function so that the joined Table
Id
can be optionally aliased.
{as: (joinedTableId: string): void}
Type | Description | |
---|---|---|
as | as(joinedTableId: string): void |
Note that if two Join
clauses are both aliased to the same name (or if you create two joins to the same underlying Table
, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that joins the same underlying Table
twice, for different purposes. Both joins are aliased with the 'as' function to disambiguate them. Note that the selected Cells are also aliased.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', buyerId: '1', sellerId: '2'},
felix: {species: 'cat', buyerId: '2'},
cujo: {species: 'dog', buyerId: '3', sellerId: '1'},
})
.setTable('humans', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('buyers', 'name').as('buyer');
select('sellers', 'name').as('seller');
// from pets
join('humans', 'buyerId').as('buyers');
join('humans', 'sellerId').as('sellers');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {buyer: 'Alice', seller: 'Bob'}}
// -> {felix: {buyer: 'Bob'}}
// -> {cujo: {buyer: 'Carol', seller: 'Alice'}}
Since
v2.0.0
Select
The Select
type describes a function that lets you specify a Cell
or calculated value for including into the query's result.
Calling this function with one Id
parameter will indicate that the query should select the value of the specified Cell
from the query's main Table
.
(cellId: string): SelectedAs
Type | Description | |
---|---|---|
cellId | string | |
returns | SelectedAs | A SelectedAs object so that the selected Cell Id can be optionally aliased. |
Calling this function with two parameters will indicate that the query should select the value of the specified Cell
from a Table
that has been joined in the query.
(
joinedTableId: string,
joinedCellId: string,
): SelectedAs
Type | Description | |
---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
returns | SelectedAs | A SelectedAs object so that the selected Cell Id can be optionally aliased. |
Calling this function with one callback parameter will indicate that the query should select a calculated value, based on one or more Cell
values in the main Table
or a joined Table
, or on the main Table
's Row
Id
.
(getCell: (getTableCell: GetTableCell, rowId: string) => CellOrUndefined): SelectedAs
Type | Description | |
---|---|---|
getCell | (getTableCell: GetTableCell, rowId: string) => CellOrUndefined | A callback that takes a |
returns | SelectedAs | A SelectedAs object so that the selected Cell Id can be optionally aliased. |
The Select
function is provided to the third query
parameter of the setQueryDefinition
method. A query definition must call the Select
function at least once, otherwise it will be meaningless and return no data.
Examples
This example shows a query that selects two Cells from the main query Table
.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown', legs: 4},
felix: {species: 'cat', color: 'black', legs: 4},
cujo: {species: 'dog', color: 'black', legs: 4},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select}) => {
select('species');
select('color');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', color: 'brown'}}
// -> {felix: {species: 'cat', color: 'black'}}
// -> {cujo: {species: 'dog', color: 'black'}}
This example shows a query that selects two Cells, one from a joined Table
.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species');
select('owners', 'name');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog', name: 'Alice'}}
// -> {felix: {species: 'cat', name: 'Bob'}}
// -> {cujo: {species: 'dog', name: 'Carol'}}
This example shows a query that calculates a value from two underlying Cells.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select(
(getTableCell, rowId) =>
`${getTableCell('species')} for ${getTableCell('owners', 'name')}`,
).as('description');
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {description: 'dog for Alice'}}
// -> {felix: {description: 'cat for Bob'}}
// -> {cujo: {description: 'dog for Carol'}}
Since
v2.0.0
SelectedAs
The SelectedAs
type describes an object returned from calling a Select
function so that the selected Cell
Id
can be optionally aliased.
{as: (selectedCellId: string): void}
Type | Description | |
---|---|---|
as | as(selectedCellId: string): void |
If you are using a callback in the Select
cause, it is highly recommended to use the 'as' function, since otherwise a machine-generated column name will be used.
Note that if two Select
clauses are both aliased to the same name (or if two columns with the same underlying name are selected, both without aliases), only the latter of two will be used in the query.
Example
This example shows a query that selects two Cells, one from a joined Table
. Both are aliased with the 'as' function:
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice'},
'2': {name: 'Bob'},
'3': {name: 'Carol'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join}) => {
select('species').as('petSpecies');
select('owners', 'name').as('ownerName');
// from pets
join('owners', 'ownerId');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {petSpecies: 'dog', ownerName: 'Alice'}}
// -> {felix: {petSpecies: 'cat', ownerName: 'Bob'}}
// -> {cujo: {petSpecies: 'dog', ownerName: 'Carol'}}
Since
v2.0.0
Where
The Where
type describes a function that lets you specify conditions to filter results, based on the underlying Cells of the main or joined Tables
.
Calling this function with two parameters is used to include only those Rows for which a specified Cell
in the query's main Table
has a specified value.
(
cellId: string,
equals: Cell,
): void
Type | Description | |
---|---|---|
cellId | string | |
equals | Cell | The value that the |
returns | void | This has no return value. |
Calling this function with three parameters is used to include only those Rows for which a specified Cell
in a joined Table
has a specified value.
(
joinedTableId: string,
joinedCellId: string,
equals: Cell,
): void
Type | Description | |
---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
equals | Cell | The value that the |
returns | void | This has no return value. |
Calling this function with one callback parameter is used to include only those Rows which meet a calculated boolean condition, based on values in the main and (optionally) joined Tables
.
(condition: (getTableCell: GetTableCell) => boolean): void
Type | Description | |
---|---|---|
condition | (getTableCell: GetTableCell) => boolean | A callback that takes a |
returns | void | This has no return value. |
The Where
function is provided to the third query
parameter of the setQueryDefinition
method.
If you do not specify a Where
clause, you should expect every non-empty Row
of the main Table
to appear in the query's results.
A Where
condition has to be true for a Row
to be included in the results. Each Where
class is additive, as though combined with a logical 'and'. If you wish to create an 'or' expression, use the single parameter version of the type that allows arbitrary programmatic conditions.
The Where
keyword differs from the Having
keyword in that the former describes conditions that should be met by underlying Cell
values (whether selected or not), and the latter describes conditions based on calculated and aggregated values - after Group
clauses have been applied.
Examples
This example shows a query that filters the results from a single Table
by comparing an underlying Cell
from it with a value.
const store = createStore().setTable('pets', {
fido: {species: 'dog'},
felix: {species: 'cat'},
cujo: {species: 'dog'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, where}) => {
select('species');
where('species', 'dog');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog'}}
// -> {cujo: {species: 'dog'}}
This example shows a query that filters the results of a query by comparing an underlying Cell
from a joined Table
with a value. Note that the joined table has also been aliased, and so its alias is used in the Where
clause.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, where}) => {
select('species');
// from pets
join('owners', 'ownerId').as('petOwners');
where('petOwners', 'state', 'CA');
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {fido: {species: 'dog'}}
// -> {felix: {species: 'cat'}}
This example shows a query that filters the results of a query with a condition that is calculated from underlying Cell
values from the main and joined Table
. Note that the joined table has also been aliased, and so its alias is used in the Where
clause.
const store = createStore()
.setTable('pets', {
fido: {species: 'dog', ownerId: '1'},
felix: {species: 'cat', ownerId: '2'},
cujo: {species: 'dog', ownerId: '3'},
})
.setTable('owners', {
'1': {name: 'Alice', state: 'CA'},
'2': {name: 'Bob', state: 'CA'},
'3': {name: 'Carol', state: 'WA'},
});
const queries = createQueries(store);
queries.setQueryDefinition('query', 'pets', ({select, join, where}) => {
select('species');
select('petOwners', 'state');
// from pets
join('owners', 'ownerId').as('petOwners');
where(
(getTableCell) =>
getTableCell('pets', 'species') === 'cat' ||
getTableCell('petOwners', 'state') === 'WA',
);
});
queries.forEachResultRow('query', (rowId) => {
console.log({[rowId]: queries.getResultRow('query', rowId)});
});
// -> {felix: {species: 'cat', state: 'CA'}}
// -> {cujo: {species: 'dog', state: 'WA'}}
Since
v2.0.0
Listener type aliases
This is the collection of listener type aliases within the queries
module. There are 6 listener type aliases in total.
ResultTableListener
The ResultTableListener
type describes a function that is used to listen to changes to a query's result Table
.
(
queries: Queries,
tableId: Id,
getCellChange: GetCellChange | undefined,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | This has no return value. |
A ResultTableListener
is provided when using the addResultTableListener
method. See that method for specific examples.
When called, a ResultTableListener
is given a reference to the Queries
object, the Id
of the Table
that changed (which is the same as the query Id
), and a GetCellChange
function that can be used to query Cell
values before and after the change.
Since
v2.0.0
ResultRowIdsListener
The ResultRowIdsListener
type describes a function that is used to listen to changes to the Row
Ids
in a query's result Table
.
(
queries: Queries,
tableId: Id,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
returns | void | This has no return value. |
A ResultRowIdsListener
is provided when using the addResultRowIdsListener
method. See that method for specific examples.
When called, a ResultRowIdsListener
is given a reference to the Queries
object, and the Id
of the Table
whose Row
Ids
changed (which is the same as the query Id
).
Since
v2.0.0
ResultSortedRowIdsListener
The ResultSortedRowIdsListener
type describes a function that is used to listen to changes to the sorted Row
Ids
in a query's result Table
.
(
queries: Queries,
tableId: Id,
cellId: Id | undefined,
descending: boolean,
offset: number,
limit: number | undefined,
sortedRowIds: Ids,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
cellId | Id | undefined | |
descending | boolean | Whether the sorting was in descending order. |
offset | number | |
limit | number | undefined | |
sortedRowIds | Ids | |
returns | void | This has no return value. |
A ResultSortedRowIdsListener
is provided when using the addResultSortedRowIdsListener
method. See that method for specific examples.
When called, a ResultSortedRowIdsListener
is given a reference to the Queries
object, the Id
of the Table
whose Row
Ids
changed (which is the same as the query Id
), the Cell
Id
being used to sort them, whether descending or not, and the offset and limit of the number of Ids
returned, for pagination purposes. It also receives the sorted array of Ids
itself, so that you can use them in the listener without the additional cost of an explicit call to getResultSortedRowIds.
Since
v2.0.0
ResultRowListener
The ResultRowListener
type describes a function that is used to listen to changes to a Row
in a query's result Table
.
(
queries: Queries,
tableId: Id,
rowId: Id,
getCellChange: GetCellChange | undefined,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
getCellChange | GetCellChange | undefined | A function that returns information about any |
returns | void | This has no return value. |
A ResultRowListener
is provided when using the addResultRowListener
method. See that method for specific examples.
When called, a ResultRowListener
is given a reference to the Queries
object, the Id
of the Table
that changed (which is the same as the query Id
), the Id
of the Row
that changed, and a GetCellChange
function that can be used to query Cell
values before and after the change.
Since
v2.0.0
ResultCellIdsListener
The ResultCellIdsListener
type describes a function that is used to listen to changes to the Cell
Ids
in a Row
in a query's result Table
.
(
queries: Queries,
tableId: Id,
rowId: Id,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
rowId | Id | |
returns | void | This has no return value. |
A ResultCellIdsListener
is provided when using the addResultCellIdsListener
method. See that method for specific examples.
When called, a ResultCellIdsListener
is given a reference to the Queries
object, the Id
of the Table
that changed (which is the same as the query Id
), and the Id
of the Row
whose Cell
Ids
changed.
Since
v2.0.0
ResultCellListener
The ResultCellListener
type describes a function that is used to listen to changes to a Cell
in a query's result Table
.
(
queries: Queries,
tableId: Id,
rowId: Id,
cellId: Id,
newCell: Cell,
oldCell: Cell,
getCellChange: GetCellChange | undefined,
): void
Type | Description | |
---|---|---|
queries | Queries | A reference to the |
tableId | Id | The |
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 ResultCellListener
is provided when using the addResultCellListener
method. See that method for specific examples.
When called, a ResultCellListener
is given a reference to the Queries
object, the Id
of the Table
that changed (which is the same as the query Id
), 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 change.
Since
v2.0.0
Aggregators type aliases
This is the collection of aggregators type aliases within the queries
module. There are 4 aggregators type aliases in total.
Aggregate
The Aggregate type describes a custom function that takes an array of Cell
values and returns an aggregate of them.
(
cells: Cell[],
length: number,
): Cell
Type | Description | |
---|---|---|
cells | Cell[] | The array of |
length | number | The length of the array of |
returns | Cell | The value of the aggregation. |
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.
Since
v2.0.0
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.
(
current: Cell,
add: Cell,
length: number,
): Cell | undefined
Type | Description | |
---|---|---|
current | Cell | The current value of the aggregation. |
add | Cell | The |
length | number | The length of the array of |
returns | Cell | undefined | The new value of the aggregation. |
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 aggregation 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.
Since
v2.0.0
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.
(
current: Cell,
remove: Cell,
length: number,
): Cell | undefined
Type | Description | |
---|---|---|
current | Cell | The current value of the aggregation. |
remove | Cell | The |
length | number | The length of the array of |
returns | Cell | undefined | The new value of the aggregation. |
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 aggregation 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.
Since
v2.0.0
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.
(
current: Cell,
add: Cell,
remove: Cell,
length: number,
): Cell | undefined
Type | Description | |
---|---|---|
current | Cell | The current value of the aggregation. |
add | Cell | The |
remove | Cell | The |
length | number | The length of the array of |
returns | Cell | undefined | The new value of the aggregation. |
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 aggregation 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.
Since
v2.0.0
Callback type aliases
This is the collection of callback type aliases within the queries
module. There are only two callback type aliases, GetTableCell
and QueryCallback
.
GetTableCell
The GetTableCell
type describes a function that takes a Id
and returns the Cell
value for a particular Row
, optionally in a joined Table
.
When called with one parameter, this function will return the value of the specified Cell
from the query's main Table
for the Row
being selected or filtered.
(cellId: string): CellOrUndefined
Type | Description | |
---|---|---|
cellId | string | |
returns | CellOrUndefined | A Cell value or `undefined`. |
When called with two parameters, this function will return the value of the specified Cell
from a Table
that has been joined in the query, for the Row
being selected or filtered.
(
joinedTableId: string,
joinedCellId: string,
): CellOrUndefined
Type | Description | |
---|---|---|
joinedTableId | string | The |
joinedCellId | string | |
returns | CellOrUndefined | A Cell value or `undefined`. |
A GetTableCell
can be provided when setting query definitions, specifically in the Select
and Where
clauses when you want to create or filter on calculated values. See those methods for specific examples.
Since
v2.0.0
QueryCallback
The QueryCallback
type describes a function that takes a query's Id
.
(queryId: Id): void
Type | Description | |
---|---|---|
queryId | Id | The |
returns | void | This has no return value. |
A QueryCallback
is provided when using the forEachQuery
method, so that you can do something based on every query in the Queries
object. See that method for specific examples.
Since
v2.0.0
Development type aliases
This is the collection of development type aliases within the queries
module. There is only one type alias, QueriesListenerStats
.
QueriesListenerStats
The QueriesListenerStats
type describes the number of listeners registered with the Queries
object, and can be used for debugging purposes.
{
table?: number;
rowIds?: number;
row?: number;
cellIds?: number;
cell?: number;
}
Type | Description | |
---|---|---|
table? | number | The number of |
rowIds? | number | The number of |
row? | number | The number of |
cellIds? | number | The number of |
cell? | number | The number of |
A QueriesListenerStats
object is returned from the getListenerStats method, and is only populated in a debug build.
Since
v2.0.0
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
Drawing 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 |
checkpoint? | number | The number of |
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
Drawing 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 | The number of times data has been loaded. |
saves? | number | The number of times data has been saved. |
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 Queries
guide
Building UIs With Checkpoints
guide
Countries demo
Todo App demos
Drawing 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);
Queries hooks
This is the collection of queries hooks within the ui-react
module. There are 14 queries hooks in total.
useResultTable
The useResultTable
hook returns an object containing the entire data of the result Table
of the given query, and registers a listener so that any changes to that result will cause a re-render.
useResultTable(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Table
Type | Description | |
---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Table | An object containing the entire data of the result Table. |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultTable
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the query result will cause a re-render. When the component containing this hook is unmounted, the listener will be automatically removed.
Examples
This example creates a Queries
object outside the application, which is used in the useTable
hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultTable('dogColors', queries))}</span>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
store.setCell('pets', 'fido', 'color', 'walnut');
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"walnut"},"cujo":{"color":"black"}}</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultTable
hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTable('dogColors'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultTable
hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultTable('dogColors', 'petQueries'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>{"fido":{"color":"brown"},"cujo":{"color":"black"}}</span>'
Since
v2.0.0
useResultTableListener
The useResultTableListener
hook registers a listener function with a Queries
object that will be called whenever data in a result Table
changes.
useResultTableListener(
queryId: IdOrNull,
listener: ResultTableListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultTableListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | 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 useResultTable
hook).
You can either listen to a single result Table
(by specifying a query Id
as the method's first parameter) or changes to any result Table
(by providing a null
wildcard).
Unlike the addResultTableListener
method, which returns a listener Id
and requires you to remove it manually, the useResultTableListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultTableListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultTableListener('petColors', () =>
console.log('Result table changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(queries.getListenerStats().table);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result table changed'
ReactDOM.unmountComponentAtNode(app);
console.log(queries.getListenerStats().table);
// -> 0
Since
v2.0.0
useResultRowIds
The useResultRowIds
hook returns the Ids
of every Row
in the result Table
of the given query, and registers a listener so that any changes to those Ids
will cause a re-render.
useResultRowIds(
queryId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Ids | An array of the Ids of every Row in the result of the query. |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultRowIds
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result 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 Queries
object outside the application, which is used in the useResultRowIds
hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultRowIds('dogColors', queries))}</span>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultRowIds
hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRowIds('dogColors'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultRowIds
hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRowIds('dogColors', 'petQueries'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>["fido","cujo"]</span>'
Since
v2.0.0
useResultRowIdsListener
The useResultRowIdsListener
hook registers a listener function with a Queries
object that will be called whenever the Row
Ids
in a result Table
change.
useResultRowIdsListener(
queryId: IdOrNull,
listener: ResultRowIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
listener | ResultRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | 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 useResultRowIds
hook).
You can either listen to a single result Table
(by specifying a query Id
as the method's first parameter) or changes to any result Table
(by providing a null
wildcard).
Unlike the addResultRowIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useResultRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowIdsListener('petColors', () =>
console.log('Result Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(queries.getListenerStats().rowIds);
// -> 1
store.setRow('pets', 'rex', {species: 'dog', color: 'tan'});
// -> 'Result Row Ids changed'
ReactDOM.unmountComponentAtNode(app);
console.log(queries.getListenerStats().rowIds);
// -> 0
Since
v2.0.0
useResultSortedRowIds
The useResultSortedRowIds
hook returns the sorted (and optionally, paginated) Ids
of every Row
in the result Table
of the given query, and registers a listener so that any changes to those Ids
will cause a re-render.
useResultSortedRowIds(
queryId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Ids | An array of the Ids of every Row in the result of the query. |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultSortedRowIds
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the sorted result 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 Queries
object outside the application, which is used in the useResultSortedRowIds
hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(
useResultSortedRowIds(
'dogColors',
'color',
false,
0,
undefined,
queries,
),
)}
</span>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
store.setCell('pets', 'cujo', 'species', 'wolf');
console.log(app.innerHTML);
// -> '<span>["fido"]</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultSortedRowIds
hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultSortedRowIds('dogColors', 'color'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultSortedRowIds
hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useResultSortedRowIds(
'dogColors',
'color',
false,
0,
undefined,
'petQueries',
),
)}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>["cujo","fido"]</span>'
Since
v2.0.0
useResultSortedRowIdsListener
The useResultSortedRowIdsListener
hook registers a listener function with a Queries
object that will be called whenever the sorted (and optionally, paginated) Row
Ids
in a result Table
change.
useResultSortedRowIdsListener(
queryId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: ResultRowIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | string | The |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | ResultRowIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | 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 useResultSortedRowIds
hook).
Unlike the addResultSortedRowIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useResultSortedRowIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultSortedRowIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultSortedRowIdsListener(
'petColors',
'color',
false,
0,
undefined,
() => console.log('Sorted result Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(queries.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {color: 'tan'});
// -> 'Sorted result Row Ids changed'
ReactDOM.unmountComponentAtNode(app);
console.log(queries.getListenerStats().sortedRowIds);
// -> 0
Since
v2.0.0
useResultRow
The useResultRow
hook returns an object containing the entire data of a single Row
in the result Table
of the given query, and registers a listener so that any changes to that Row
will cause a re-render.
useResultRow(
queryId: string,
rowId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Row
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Row | An object containing the entire data of the Row in the result Table of the query. |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultRow
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result 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 Queries
object outside the application, which is used in the useResultRow
hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
const App = () => (
<span>{JSON.stringify(useResultRow('dogColors', 'fido', queries))}</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 Queries
object is provided. A component within it then uses the useResultRow
hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultRow('dogColors', 'fido'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultRow
hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultRow('dogColors', 'fido', 'petQueries'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>{"color":"brown"}</span>'
Since
v2.0.0
useResultRowListener
The useResultRowListener
hook registers a listener function with a Queries
object that will be called whenever data in a result Row
changes.
useResultRowListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultRowListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultRowListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | 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 useResultRow
hook).
You can either listen to a single result Row
(by specifying a query Id
and Row
Id
as the method's first two parameters) or changes to any result Row
(by providing null
wildcards).
Both, either, or neither of the queryId
and rowId
parameters can be wildcarded with null
. You can listen to a specific result Row
in a specific query, any result Row
in a specific query, a specific result Row
in any query, or any result Row
in any query.
Unlike the addResultRowListener
method, which returns a listener Id
and requires you to remove it manually, the useResultRowListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultRowListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultRowListener('petColors', 'fido', () =>
console.log('Result row changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(queries.getListenerStats().row);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result row changed'
ReactDOM.unmountComponentAtNode(app);
console.log(queries.getListenerStats().row);
// -> 0
Since
v2.0.0
useResultCellIds
The useResultCellIds
hook returns the Ids
of every Cell
in a given Row
in the result Table
of the given query, and registers a listener so that any changes to those Ids
will cause a re-render.
useResultCellIds(
queryId: string,
rowId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Ids
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Ids | An array of the Ids of every Cell in the Row in the result of the query. |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultCellIds
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result 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 Queries
object outside the application, which is used in the useResultCellIds
hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>
{JSON.stringify(useResultCellIds('dogColors', 'fido', queries))}
</span>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
store.setCell('pets', 'fido', 'legs', 4);
console.log(app.innerHTML);
// -> '<span>["species","color","legs"]</span>'
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useResultCellIds
hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{JSON.stringify(useResultCellIds('dogColors', 'fido'))}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultCellIds
hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(useResultCellIds('dogColors', 'fido', 'petQueries'))}
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>["species","color"]</span>'
Since
v2.0.0
useResultCellIdsListener
The useResultCellIdsListener
hook registers a listener function with a Queries
object that will be called whenever the Cell
Ids
in a result Row
change.
useResultCellIdsListener(
queryId: IdOrNull,
rowId: IdOrNull,
listener: ResultCellIdsListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
listener | ResultCellIdsListener | The function that will be called whenever the |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | 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 useResultCellIds
hook).
Both, either, or neither of the queryId
and rowId
parameters can be wildcarded with null
. You can listen to a specific result Row
in a specific query, any result Row
in a specific query, a specific result Row
in any query, or any result Row
in any query.
Unlike the addResultCellIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useResultCellIdsListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultCellIdsListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultCellIdsListener('petColors', 'fido', () =>
console.log('Result cell Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select, where}) => {
select('color');
select('legs');
},
);
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(queries.getListenerStats().cellIds);
// -> 1
store.setCell('pets', 'fido', 'legs', 4);
// -> 'Result cell Ids changed'
ReactDOM.unmountComponentAtNode(app);
console.log(queries.getListenerStats().cellIds);
// -> 0
Since
v2.0.0
useResultCell
The useResultCell
hook returns the value of a single Cell
in a given Row
in the result Table
of the given query, and registers a listener so that any changes to that value will cause a re-render.
useResultCell(
queryId: string,
rowId: string,
cellId: string,
queriesOrQueriesId?: QueriesOrQueriesId,
): Cell | undefined
Type | Description | |
---|---|---|
queryId | string | The |
rowId | string | |
cellId | string | |
queriesOrQueriesId? | QueriesOrQueriesId | The |
returns | Cell | undefined | The value of the Cell, or `undefined`. |
A Provider
component is used to wrap part of an application in a context, and it can contain a default Queries
object or a set of Queries
objects named by Id
. The useResultCell
hook lets you indicate which Queries
object to get data for: omit the final optional final parameter for the default context Queries
object, provide an Id
for a named context Queries
object, or provide a Queries
object explicitly by reference.
When first rendered, this hook will create a listener so that changes to the result 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 Queries
object outside the application, which is used in the useResultCell
hook by reference. A change to the data in the query re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('species');
select('color');
select('legs');
where('species', 'dog');
},
);
const App = () => (
<span>{useResultCell('dogColors', 'fido', 'color', queries)}</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 Queries
object is provided. A component within it then uses the useResultCell
hook.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultCell('dogColors', 'fido', 'color')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useResultCell
hook.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useResultCell('dogColors', 'fido', 'color', 'petQueries')}</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('dogColors', 'pets', ({select, where}) => {
select('species');
select('color');
where('species', 'dog');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v2.0.0
useResultCellListener
The useResultCellListener
hook registers a listener function with a Queries
object that will be called whenever data in a Cell
changes.
useResultCellListener(
queryId: IdOrNull,
rowId: IdOrNull,
cellId: IdOrNull,
listener: ResultCellListener,
listenerDeps?: DependencyList,
queriesOrQueriesId?: QueriesOrQueriesId,
): void
Type | Description | |
---|---|---|
queryId | IdOrNull | The |
rowId | IdOrNull | The |
cellId | IdOrNull | The |
listener | ResultCellListener | The function that will be called whenever data in the matching result |
listenerDeps? | DependencyList | An optional array of dependencies for the |
queriesOrQueriesId? | QueriesOrQueriesId | 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 useResultCell
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 queryId
, rowId
, and cellId
parameters can be wildcarded with null
. You can listen to a specific Cell
in a specific result Row
in a specific query, any Cell
in any result Row
in any query, for example - or every other combination of wildcards.
Unlike the addResultCellListener
method, which returns a listener Id
and requires you to remove it manually, the useResultCellListener
hook manages this lifecycle for you: when the listener changes (per its listenerDeps
dependencies) or the component unmounts, the listener on the underlying Queries
object will be deleted.
Example
This example uses the useResultCellListener
hook to create a listener that is scoped to a single component. When the component is unmounted, the listener is removed from the Queries
object.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => {
useResultCellListener('petColors', 'fido', 'color', () =>
console.log('Result cell changed'),
);
return <span>App</span>;
};
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(queries.getListenerStats().cell);
// -> 1
store.setCell('pets', 'fido', 'color', 'walnut');
// -> 'Result cell changed'
ReactDOM.unmountComponentAtNode(app);
console.log(queries.getListenerStats().cell);
// -> 0
Since
v2.0.0
useCreateQueries
The useCreateQueries
hook is used to create a Queries
object within a React application with convenient memoization.
useCreateQueries(
store: Store,
create: (store: Store) => Queries,
createDeps?: DependencyList,
): Queries
Type | Description | |
---|---|---|
store | Store | A reference to the |
create | (store: Store) => Queries | An optional callback for performing post-creation steps on the |
createDeps? | DependencyList | An optional array of dependencies for the |
returns | Queries | A reference to the Queries object. |
It is possible to create a Queries
object outside of the React app with the regular createQueries
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 Queries
object being created every time the app renders or re-renders, the useCreateQueries
hook wraps the creation in a memoization.
The useCreateQueries
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 Queries
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 Queries
object is destroyed whenever a new one is created or the component is unmounted.
Examples
This example creates a Queries
object at the top level of a React application. Even though the App component is rendered twice, the Queries
object creation only occurs once by default.
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
);
const queries = useCreateQueries(store, (store) => {
console.log('Queries created');
return createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
});
return (
<span>{queries.getResultCell('dogColors', 'fido', 'color')}</span>
);
};
const app = document.createElement('div');
ReactDOM.render(<App />, app);
// -> 'Queries created'
ReactDOM.render(<App />, app);
// No second Queries creation
console.log(app.innerHTML);
// -> '<span>brown</span>'
This example creates a Queries
object at the top level of a React application. The App component is rendered twice, each with a different top-level prop. The useCreateQueries
hook takes the resultCell
prop as a dependency, and so the Queries
object is created again on the second render.
const App = () => {
const store = useCreateStore(() =>
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
);
const queries = useCreateQueries(store, (store) => {
console.log('Queries created');
return createQueries(store).setQueryDefinition(
'dogColors',
'pets',
({select, where}) => {
select('color');
where('species', 'dog');
},
);
});
return (
<span>{queries.getResultCell('dogColors', 'fido', 'color')}</span>
);
};
const app = document.createElement('div');
ReactDOM.render(<App />, app);
// -> 'Queries created'
ReactDOM.render(<App />, app);
// No second Queries creation
console.log(app.innerHTML);
// -> '<span>brown</span>'
Since
v2.0.0
useQueries
The useQueries
hook is used to get a reference to a Queries
object from within a Provider
component context.
useQueries(id?: string): Queries | undefined
Type | Description | |
---|---|---|
id? | string | An optional |
returns | Queries | undefined | A reference to the Queries object (or `undefined` if not within a Provider context, or if the requested Queries object does not exist) |
A Provider
component is used to wrap part of an application in a context. It can contain a default Queries
object (or a set of Queries
objects named by Id
) that can be easily accessed without having to be passed down as props through every component.
The useQueries
hook lets you either get a reference to the default Queries
object (when called without an parameter), or one of the Queries
objects that are named by Id
(when called with an Id
parameter).
Examples
This example creates a Provider context into which a default Queries
object is provided. A component within it then uses the useQueries
hook to get a reference to the Queries
object again, without the need to have it passed as a prop.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => <span>{useQueries().getListenerStats().table}</span>;
const queries = createQueries(createStore());
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'
This example creates a Provider context into which a Queries
object is provided, named by Id
. A component within it then uses the useQueries
hook with that Id
to get a reference to the Queries
object again, without the need to have it passed as a prop.
const App = ({queries}) => (
<Provider queriesById={{petQueries: queries}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>{useQueries('petQueries').getListenerStats().table}</span>
);
const queries = createQueries(createStore());
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>0</span>'
Since
v2.0.0
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 28 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
useSortedRowIds
The useSortedRowIds
hook returns the sorted (and optionally, paginated) Ids
of every Row
in a given Table
, and registers a listener so that any changes to that result will cause a re-render.
useSortedRowIds(
tableId: string,
cellId?: string,
descending?: boolean,
offset?: number,
limit?: number,
storeOrStoreId?: StoreOrStoreId,
): Ids
Type | Description | |
---|---|---|
tableId | string | |
cellId? | string | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | The number of |
limit? | number | The maximum number of |
storeOrStoreId? | StoreOrStoreId | The |
returns | Ids | An array of the sorted 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 useSortedRowIds
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 sorted 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 useSortedRowIds
hook by reference. A change to the data in the Store
re-renders the component.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<span>
{JSON.stringify(
useSortedRowIds('pets', 'species', false, 0, undefined, store),
)}
</span>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<span>["felix","fido","cujo"]</span>'
This example creates a Provider context into which a default Store
is provided. A component within it then uses the useSortedRowIds
hook.
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => <span>{JSON.stringify(useSortedRowIds('pets'))}</span>;
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
This example creates a Provider context into which a Store
is provided, named by Id
. A component within it then uses the useSortedRowIds
hook.
const App = ({store}) => (
<Provider storesById={{petStore: store}}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
{JSON.stringify(
useSortedRowIds('pets', 'species', false, 0, undefined, 'petStore'),
)}
</span>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<span>["felix","fido"]</span>'
Since
v2.0.0
useSortedRowIdsListener
The useSortedRowIdsListener
hook registers a listener function with a Store
that will be called whenever sorted (and optionally, paginated) Row
Ids
in a Table
change.
useSortedRowIdsListener(
tableId: string,
cellId: undefined | string,
descending: boolean,
offset: number,
limit: undefined | number,
listener: SortedRowIdsListener,
listenerDeps?: DependencyList,
mutator?: boolean,
storeOrStoreId?: StoreOrStoreId,
): void
Type | Description | |
---|---|---|
tableId | string | |
cellId | undefined | string | The |
descending | boolean | Whether the sorting should be in descending order. |
offset | number | The number of |
limit | undefined | number | The maximum number of |
listener | SortedRowIdsListener | The function that will be called whenever the sorted |
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 useSortedRowIds
hook).
Unlike the addSortedRowIdsListener
method, which returns a listener Id
and requires you to remove it manually, the useSortedRowIdsListener
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 useSortedRowIdsListener
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 = () => {
useSortedRowIdsListener('pets', 'species', false, 0, undefined, () =>
console.log('Sorted Row Ids changed'),
);
return <span>App</span>;
};
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(store.getListenerStats().sortedRowIds);
// -> 1
store.setRow('pets', 'cujo', {species: 'wolf'});
// -> 'Sorted Row Ids changed'
ReactDOM.unmountComponentAtNode(app);
console.log(store.getListenerStats().sortedRowIds);
// -> 0
Since
v2.0.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
, Queries
, 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>'
Queries components
This is the collection of queries components within the ui-react
module. There are 4 queries components in total.
ResultSortedTableView
The ResultSortedTableView
component renders the contents of a single query's sorted result Table
in a Queries
object, and registers a listener so that any changes to that result will cause a re-render.
ResultSortedTableView(props: ResultSortedTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultSortedTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the result Table, or nothing, if not present. |
The component's props identify which Table
to render based on query Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or by explicit reference). It also takes a Cell
Id
to sort by and a boolean to indicate that the sorting should be in descending order. The offset
and limit
props are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
This component renders a result Table
by iterating over its Row
objects, in the order dictated by the sort parameters. By default these are in turn rendered with the ResultRowView
component, but you can override this behavior by providing a resultRowComponent
prop, a custom component of your own that will render a Row
based on ResultRowProps
. You can also pass additional props to your custom component with the getResultRowComponentProps
callback prop.
This component uses the useResultSortedRowIds
hook under the covers, which means that any changes to the structure or sorting of the result Table
will cause a re-render.
Examples
This example creates a Queries
object outside the application, which is used in the ResultSortedTableView
component by reference. A change to the data in the Store
re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
queries={queries}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>black/brown</div>'
store.setCell('pets', 'felix', 'color', 'white');
console.log(app.innerHTML);
// -> '<div>brown/white</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultSortedTableView
component within it then renders the Table
(with Ids
for readability).
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
debugIds={true}
/>
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<div>petColors:{felix:{color:{black}}fido:{color:{brown}}}</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultSortedTableView
component within it then renders the Table
with a custom Row
component and a custom props callback.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultSortedTableView
queryId="petColors"
cellId="color"
resultRowComponent={FormattedRowView}
getResultRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({queryId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<ResultRowView queryId={queryId} rowId={rowId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<div><span>felix: black</span><span><b>fido</b>: brown</span></div>'
Since
v2.0.0
ResultTableView
The ResultTableView
component renders the contents of a single query's result Table
in a Queries
object, and registers a listener so that any changes to that result will cause a re-render.
ResultTableView(props: ResultTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultTableProps | The props for this component. |
returns | ComponentReturnType | A rendering of the result Table, or nothing, if not present. |
The component's props identify which Table
to render based on query Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or by explicit reference).
This component renders a result Table
by iterating over its Row
objects. By default these are in turn rendered with the ResultRowView
component, but you can override this behavior by providing a resultRowComponent
prop, a custom component of your own that will render a Row
based on ResultRowProps
. You can also pass additional props to your custom component with the getResultRowComponentProps
callback prop.
This component uses the useResultRowIds
hook under the covers, which means that any changes to the structure of the result Table
will cause a re-render.
Examples
This example creates a Queries
object outside the application, which is used in the ResultTableView
component by reference. A change to the data in the Store
re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<div>
<ResultTableView queryId="petColors" queries={queries} separator="/" />
</div>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>brown/black</div>'
store.setRow('pets', 'cujo', {species: 'dog', color: 'black'});
console.log(app.innerHTML);
// -> '<div>brown/black/black</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultTableView
component within it then renders the Table
(with Ids
for readability).
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultTableView queryId="petColors" debugIds={true} />
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<div>petColors:{fido:{color:{brown}}felix:{color:{black}}}</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultTableView
component within it then renders the Table
with a custom Row
component and a custom props callback.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultTableView
queryId="petColors"
resultRowComponent={FormattedRowView}
getResultRowComponentProps={(rowId) => ({bold: rowId == 'fido'})}
/>
</div>
);
const FormattedRowView = ({queryId, rowId, bold}) => (
<span>
{bold ? <b>{rowId}</b> : rowId}
{': '}
<ResultRowView queryId={queryId} rowId={rowId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>fido</b>: brown</span><span>felix: black</span></div>'
Since
v2.0.0
ResultRowView
The ResultRowView
component renders the contents of a single Row
in a given query's result Table
, and registers a listener so that any changes to that result will cause a re-render.
ResultRowView(props: ResultRowProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultRowProps | The props for this component. |
returns | ComponentReturnType | A rendering of the result Row, or nothing, if not present. |
The component's props identify which Row
to render based on query Id
, Row
Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, or an explicit reference).
This component renders a Row
by iterating over its Cell
values. By default these are in turn rendered with the ResultCellView
component, but you can override this behavior by providing a resultCellComponent
prop, a custom component of your own that will render a Cell
based on ResultCellProps
. You can also pass additional props to your custom component with the getResultCellComponentProps
callback prop.
You can create your own ResultRowView-like component to customize the way that a result Row
is rendered: see the ResultTableView
component for more details.
This component uses the useResultCellIds
hook under the covers, which means that any changes to the structure of the result Row
will cause a re-render.
Examples
This example creates a Queries
object outside the application, which is used in the ResultRowView
component by reference. A change to the data in the Store
re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => {
select('species');
select('color');
},
);
const App = () => (
<div>
<ResultRowView
queryId="petColors"
rowId="fido"
queries={queries}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>dog/brown</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 Queries
object is provided. The ResultRowView
component within it then renders the Row
(with Ids
for readability).
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultRowView queryId="petColors" rowId="fido" debugIds={true} />
</div>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => {
select('species');
select('color');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<div>fido:{species:{dog}color:{brown}}</div>'
This example creates a Provider context into which a default Queries
object is provided. The ResultRowView
component within it then renders the Row
with a custom Cell
component and a custom props callback.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<ResultRowView
queryId="petColors"
rowId="fido"
resultCellComponent={FormattedResultCellView}
getResultCellComponentProps={(cellId) => ({
bold: cellId == 'species',
})}
/>
</div>
);
const FormattedResultCellView = ({queryId, rowId, cellId, bold}) => (
<span>
{bold ? <b>{cellId}</b> : cellId}
{': '}
<ResultCellView queryId={queryId} rowId={rowId} cellId={cellId} />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => {
select('species');
select('color');
});
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<div><span><b>species</b>: dog</span><span>color: brown</span></div>'
Since
v2.0.0
ResultCellView
The ResultCellView
component renders the value of a single Cell
in a given Row
, in a given query's result Table
, and registers a listener so that any changes to that result will cause a re-render.
ResultCellView(props: ResultCellProps): ComponentReturnType
Type | Description | |
---|---|---|
props | ResultCellProps | The props for this component. |
returns | ComponentReturnType | A rendering of the result Cell, or nothing, if not present. |
The component's props identify which Cell
to render based on query Id
, Row
Id
, Cell
Id
, and Queries
object (which is either the default context Queries
object, a named context Queries
object, 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 ResultCellView-like component to customize the way that a Cell
is rendered: see the ResultRowView
component for more details.
This component uses the useResultCell
hook under the covers, which means that any changes to the specified Cell
will cause a re-render.
Examples
This example creates a Queries
object outside the application, which is used in the ResultCellView
component by reference. A change to the data in the Store
re-renders the component.
const store = createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
});
const queries = createQueries(store).setQueryDefinition(
'petColors',
'pets',
({select}) => select('color'),
);
const App = () => (
<span>
<ResultCellView
queryId="petColors"
rowId="fido"
cellId="color"
queries={queries}
/>
</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 Queries
object is provided. The ResultCellView
component within it then renders the Cell
(with its Id
for readability).
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ResultCellView
queryId="petColors"
rowId="fido"
cellId="color"
debugIds={true}
/>
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span>color:{brown}</span>'
This example creates a Provider context into which a default Queries
object is provided. The ResultCellView
component within it then attempts to render a non-existent Cell
.
const App = ({queries}) => (
<Provider queries={queries}>
<Pane />
</Provider>
);
const Pane = () => (
<span>
<ResultCellView queryId="petColors" rowId="fido" cellId="height" />
</span>
);
const queries = createQueries(
createStore().setTable('pets', {
fido: {species: 'dog', color: 'brown'},
felix: {species: 'cat', color: 'black'},
cujo: {species: 'dog', color: 'black'},
}),
).setQueryDefinition('petColors', 'pets', ({select}) => select('color'));
const app = document.createElement('div');
ReactDOM.render(<App queries={queries} />, app);
console.log(app.innerHTML);
// -> '<span></span>'
Since
v2.0.0
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 5 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>'
SortedTableView
The SortedTableView
component renders the contents of a single sorted Table
in a Store
, and registers a listener so that any changes to that result will cause a re-render.
SortedTableView(props: SortedTableProps): ComponentReturnType
Type | Description | |
---|---|---|
props | SortedTableProps | 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). It also takes a Cell
Id
to sort by and a boolean to indicate that the sorting should be in descending order. The offset
and limit
props are used to paginate results, but default to 0
and undefined
to return all available Row
Ids
if not specified.
This component renders a Table
by iterating over its Row
objects, in the order dictated by the sort parameters. 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 useSortedRowIds
hook under the covers, which means that any changes to the structure or sorting of the Table
will cause a re-render.
Examples
This example creates a Store
outside the application, which is used in the SortedTableView
component by reference. A change to the data in the Store
re-renders the component.
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const App = () => (
<div>
<SortedTableView
tableId="pets"
cellId="species"
store={store}
separator="/"
/>
</div>
);
const app = document.createElement('div');
ReactDOM.render(<App />, app);
console.log(app.innerHTML);
// -> '<div>cat/dog</div>'
store.setRow('pets', 'cujo', {species: 'wolf'});
console.log(app.innerHTML);
// -> '<div>cat/dog/wolf</div>'
This example creates a Provider context into which a default Store
is provided. The SortedTableView
component within it then renders the Table
(with Ids
for readability).
const App = ({store}) => (
<Provider store={store}>
<Pane />
</Provider>
);
const Pane = () => (
<div>
<SortedTableView tableId="pets" cellId="species" debugIds={true} />
</div>
);
const store = createStore().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<div>pets:{felix:{species:{cat}}fido:{species:{dog}}}</div>'
This example creates a Provider context into which a default Store
is provided. The SortedTableView
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>
<SortedTableView
tableId="pets"
cellId="species"
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().setTables({
pets: {
fido: {species: 'dog'},
felix: {species: 'cat'},
},
});
const app = document.createElement('div');
ReactDOM.render(<App store={store} />, app);
console.log(app.innerHTML);
// -> '<div><span>felix: cat</span><span><b>fido</b>: dog</span></div>'
Since
v2.0.0
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 6 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
.
QueriesOrQueriesId
The QueriesOrQueriesId
type is used when you need to refer to a Queries
object in a React hook or component.
Queries | Id
In some simple cases you will already have a direct reference to the Queries
object.
This module also includes a Provider
component that can be used to wrap multiple Queries
objects into a context that can be used throughout the app. In this case you will want to refer to a Queries
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
.
Since
v2.0.0
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 21 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 |
SortedTableProps
SortedTableProps
props are used for components that refer to a single sorted Table
in a Store
, such as the SortedTableView
component.
{
tableId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
store?: StoreOrStoreId;
rowComponent?: ComponentType<RowProps>;
getRowComponentProps?: (rowId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
tableId | Id | |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
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 |
Since
v2.0.0
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 |
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 |
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 |
ResultTableProps
ResultTableProps
props are used for components that refer to a single query result Table
, such as the ResultTableView
component.
{
queryId: Id;
queries?: QueriesOrQueriesId;
resultRowComponent?: ComponentType<ResultRowProps>;
getResultRowComponentProps?: (rowId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
queryId | Id | The |
queries? | QueriesOrQueriesId | The |
resultRowComponent? | ComponentType<ResultRowProps> | A custom component for rendering each |
getResultRowComponentProps? | getResultRowComponentProps(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 |
Since
v2.0.0
ResultSortedTableProps
ResultSortedTableProps
props are used for components that refer to a single sorted query result Table
, such as the ResultSortedTableView
component.
{
queryId: Id;
cellId?: Id;
descending?: boolean;
offset?: number;
limit?: number;
queries?: QueriesOrQueriesId;
resultRowComponent?: ComponentType<ResultRowProps>;
getResultRowComponentProps?: (rowId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
queryId | Id | The |
cellId? | Id | The |
descending? | boolean | Whether the sorting should be in descending order. |
offset? | number | |
limit? | number | |
queries? | QueriesOrQueriesId | The |
resultRowComponent? | ComponentType<ResultRowProps> | A custom component for rendering each |
getResultRowComponentProps? | getResultRowComponentProps(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 |
Since
v2.0.0
ResultRowProps
ResultRowProps
props are used for components that refer to a single Row
in a query result Table
, such as the ResultRowView
component.
{
queryId: Id;
rowId: Id;
queries?: QueriesOrQueriesId;
resultCellComponent?: ComponentType<ResultCellProps>;
getResultCellComponentProps?: (cellId: string): ExtraProps;
separator?: ReactElement | string;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
queryId | Id | The |
rowId | Id | |
queries? | QueriesOrQueriesId | The |
resultCellComponent? | ComponentType<ResultCellProps> | A custom component for rendering each |
getResultCellComponentProps? | getResultCellComponentProps(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 |
Since
v2.0.0
ResultCellProps
ResultRowProps
props are used for components that refer to a single Cell
in a Row
of a result Table
, such as the ResultCellView
component.
{
queryId: Id;
rowId: Id;
cellId: Id;
queries?: QueriesOrQueriesId;
debugIds?: boolean;
}
Type | Description | |
---|---|---|
queryId | Id | The |
rowId | Id | |
cellId | Id | |
queries? | QueriesOrQueriesId | The |
debugIds? | boolean | Whether the component should also render the |
Since
v2.0.0
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
, Queries
, 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};
queries?: Queries;
queriesById?: {[queriesId: Id]: Queries};
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 |
queries? | Queries | A default single |
queriesById? | {[queriesId: Id]: Queries} | 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