# 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

```typescript filename="create-list.ts"
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.

```typescript filename="multiple-lists.ts"
// 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()](/sdk/api/lists#create).

`autoUpdate` on `lists.create()` works at runtime, but the TypeScript type for `create()` does not list it.

```typescript filename="creating-lists.ts"
// 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 |

```typescript filename="subscribe-changes.ts"
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.

```typescript filename="lifecycle.ts"
// 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 key
- `getAll()` - Get all lists
- `getAll({ modelKey })` - Get lists for a specific model
- `destroy({ listKey })` - Destroy a specific list
- `destroy({ modelKey })` - Destroy all lists of a model; `destroy()` destroys every list

```typescript filename="managing-lists.ts"
// 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.

```typescript filename="best-practices.ts"
// 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();
}
```

## Next steps

- [Query](/sdk/core-concepts/query-overview) — Master the query interface
- [Querying](/sdk/queries/query-filter) — Learn to filter list data
- [Data management](/sdk/core-concepts/data-management) — Update list data
- [Faceted search](/sdk/advanced/faceted-search) — Real-world list examples
