Lists
Filtered views of your data - create, query, and manage independent data views.
What is a list?
A DcuplList is a filtered view of your model data. Think of it as a live, queryable dataset: filter changes apply immediately, and data changes show up after dcupl.update().
Each list:
- Represents a specific model's data
- Provides a query interface (via catalog)
- Can have filters applied to it
- Recomputes its result when filters change or
dcupl.update()applies data changes - Manages its own state independently
const productList = dcupl.lists.create({
modelKey: 'Product'
});
// Access items through the catalog
const items = productList.catalog.query.items();Why lists?
Multiple views of same data
Create multiple lists for the same model, each with different filters. Great for dashboards, comparison views, or different user contexts.
Independent state
Each list maintains its own query filters, sort order, and cache. Filtering one list doesn't affect others.
Scoped lists
A list created with a query only contains the items that match it. Its own filters start empty and work on top of that subset.
// Multiple views of the same Product model
const allProducts = dcupl.lists.create({
modelKey: 'Product'
});
// Scoped list: only contains Electronics products
const electronics = dcupl.lists.create({
modelKey: 'Product',
query: {
modelKey: 'Product',
queries: [
{ attribute: 'category', operator: 'eq', value: 'Electronics' }
],
},
});
const inStock = dcupl.lists.create({
modelKey: 'Product',
query: {
modelKey: 'Product',
queries: [
{ attribute: 'inStock', operator: 'eq', value: true }
],
},
});Creating lists
Lists can be created with various options:
| Option | Description |
|---|---|
modelKey |
Required. The model to create a view of |
listKey |
Named identifier for later retrieval |
query |
Seed query. Makes a scoped list: it contains only the items that match the query when the list is created. The list's own filters start empty, and clear() does not remove the scope. |
listItemKeys |
Scope the list to these item keys instead of a query. It always serves the current items for exactly these keys, with or without autoUpdate |
autoUpdate |
Lists with a seed query only: re-run it on every dcupl.update() that affects the list, so new matches join and items that stop matching leave. A listItemKeys list serves the same items either way (with it, the list also emits list_updated on every update that affects it) |
A list created with listItemKeys or query is scoped: it keeps a set of keys (the listItemKeys you pass, or the keys the seed query matches when the list is created) and always serves the model's current items for those keys. After every dcupl.update(), also after a full rebuild (data.set(), data.reset() or a large update), changed values show, removed items drop out, and an item added under one of those keys is served. For a listItemKeys list that is all there is, so autoUpdate changes only the events, not the items. Items that start or stop matching a seed query join or leave only when you call list.update() or enable auto-update. Lists without a scope see every data change after dcupl.update(): affected lists drop their cached results and recompute on the next read. Set auto-update for all lists with new Dcupl({ performance: { autoUpdateLists: { enabled: true } } }), or per call with dcupl.update({ autoUpdateLists: true }). The full behaviour is in create().
autoUpdate on lists.create() works at runtime, but the TypeScript type for create() does not list it.
// Basic creation
const allProducts = dcupl.lists.create({ modelKey: 'Product' });
// With named key for later retrieval
const featured = dcupl.lists.create({
modelKey: 'Product',
listKey: 'featured-products',
});
const sameList = dcupl.lists.get('featured-products');
// Scoped list: only items matching the seed query
const electronics = dcupl.lists.create({
modelKey: 'Product',
query: {
modelKey: 'Product',
queries: [
{ attribute: 'category', operator: 'eq', value: 'Electronics' }
],
},
});
// Scoped list from item keys
const picked = dcupl.lists.create({
modelKey: 'Product',
listItemKeys: ['p1', 'p2'],
});
// A new Electronics product...
dcupl.data.upsert(
[{ key: 'p9', name: 'Phone', category: 'Electronics' }],
{ model: 'Product' }
);
await dcupl.update();
// ...shows up in allProducts right away. To add it to the
// scoped electronics list, re-run its seed query:
electronics.update();
// (or apply the update with dcupl.update({ autoUpdateLists: true }))Subscribe to changes
Lists emit events when their state changes. Subscribe to react to these changes.
When to update the UI
Use list.onResultChange(). It fires when the list's result can have changed:
- Filter changes -
addCondition,addGroup,clear, ... - Data changes - once per
dcupl.update()that affects the list
Reads such as items() or fn.facets() never trigger it. It returns a function that removes the subscription.
All events
list.on() receives every message of the list, including reads. Each message has a type, for example:
| Type | When |
|---|---|
result_updated |
The result can have changed (what onResultChange listens to) |
query_apply, query_remove, query_reset |
The list's query changed |
query_execute, fn_facets, ... |
A read ran |
list_updated |
list.update() was called |
const list = dcupl.lists.create({ modelKey: 'Product' });
// renderProducts: your own render function
const unsubscribe = list.onResultChange(() => {
renderProducts(list.catalog.query.items());
});
// Later: cleanup
unsubscribe();
// Every event of the list
const off = list.on((event) => {
console.log(event.type, event.listKey);
});
// React example (setProducts from useState)
useEffect(() => {
const list = dcupl.lists.create({ modelKey: 'Product' });
const unsubscribe = list.onResultChange(() => {
setProducts(list.catalog.query.items());
});
// Set initial data
setProducts(list.catalog.query.items());
return () => {
unsubscribe();
list.destroy();
};
}, []);List lifecycle
A list's life is: create (lists.create()) → query (filters and reads, any number of times) → data updates (each dcupl.update() refreshes the affected lists) → destroy (list.destroy()). A list has no state property to check; it is usable from create() until destroy().
Always destroy lists when done to prevent memory leaks.
// Create
const list = dcupl.lists.create({ modelKey: 'Product' });
// Use - apply filters
list.catalog.query.addCondition({
attribute: 'category',
operator: 'eq',
value: 'Electronics'
});
// Get results
const items = list.catalog.query.items();
// Apply data changes - the list's next read sees them
dcupl.data.upsert([{ key: 'p9', name: 'Tablet', category: 'Electronics' }], { model: 'Product' });
await dcupl.update();
// Destroy when done
list.destroy();Managing lists
dcupl provides methods to manage all your lists:
get(key)- Retrieve a list by its keygetAll()- Get all listsgetAll({ modelKey })- Get lists for a specific modeldestroy({ listKey })- Destroy a specific listdestroy({ modelKey })- Destroy all lists of a model;destroy()destroys every list
// Get list by key
const list = dcupl.lists.get('my-list');
// Get all lists
const allLists = dcupl.lists.getAll();
// Get lists for a specific model
const productLists = dcupl.lists.getAll({ modelKey: 'Product' });
// Destroy specific list
dcupl.lists.destroy({ listKey: 'my-list' });
// Or destroy via list instance
list.destroy();Best practices
Use named lists for retrieval
Give lists a listKey so you can retrieve them anywhere in your app without passing references.
Destroy lists in component cleanup
Always destroy lists when components unmount to prevent memory leaks.
Reuse lists instead of creating new ones
Change filters on an existing list rather than creating new lists for each filter change.
// Named lists for easy retrieval
const mainList = dcupl.lists.create({
modelKey: 'Product',
listKey: 'main-product-list',
});
// Retrieve anywhere
const sameList = dcupl.lists.get('main-product-list');
// Cleanup in React
useEffect(() => {
const list = dcupl.lists.create({ modelKey: 'Product' });
return () => list.destroy();
}, []);
// Reuse lists - change filters, don't recreate
const filterList = dcupl.lists.create({ modelKey: 'Product' });
function filterByCategory(category: string) {
filterList.catalog.query.clear();
filterList.catalog.query.addCondition({
attribute: 'category',
operator: 'eq',
value: category
});
return filterList.catalog.query.items();
}