vertigo 0.12.0

Reactive Real-DOM library with SSR for Rust
Documentation
# `Value::synchronize`, `ValueSynchronize`, `CollectionKey`, and the memoized list renderers

This document explains a small cluster of related building blocks in Vertigo and
how they fit together:

- [`Value::synchronize`]#valuesynchronize — the generic "mirror this reactive
  source into a derived structure" mechanism.
- [`ValueSynchronize`]#valuesynchronize-trait — the trait that a derived
  structure implements to become a valid synchronization *target*.
- [`CollectionKey`]#collectionkey — a marker trait describing how to identify
  items in a list (the key) and what the item type is.
- [`render_list_memo`]#render_list_memo and
  [`render_resource_list_memo`]#render_resource_list_memo — high-level helpers
  that render reactive lists while **memoizing each item**, so only items that
  actually changed are re-rendered.

The thread connecting all of them is `Collection<T>`: an internal structure that
turns a flat `Vec<Item>` into a list of *stable, per-item* [`Computed`](crate::Computed)s keyed by
[`CollectionKey`](crate::CollectionKey). It is created and kept up to date through `synchronize`, and it
is the data source that the memoized list renderers consume.

```text
  Source of truth                synchronize()            Per-item reactive view          Renderer
 ┌──────────────────┐         ┌────────────────────┐    ┌───────────────────────────┐   ┌──────────────────────────┐
 │ Value<Rc<Vec<T>>>│         │ ValueSynchronize   │    │ Collection<T>             │   │ render_list_memo         │
 │   or             │ ──────▶ │   (Collection<T>   │──▶ │  Vec<CollectionModel<T>>  │──▶│   /                      │
 │ LazyCache<Vec<T>>│  event  │    is the target)  │    │  each item = Computed<V>  │   │ render_resource_list_memo│
 └──────────────────┘         └────────────────────┘    └───────────────────────────┘   └──────────────────────────┘
```

---

## `Value::synchronize`

[`Value<T>`](crate::Value) is the basic reactive cell. [`synchronize`](crate::Value::synchronize) lets you create a derived,
self-updating object `R` that mirrors the value and keeps following every change:

```rust,ignore
pub fn synchronize<R: ValueSynchronize<T> + Clone + 'static>(&self)
    -> (R, DropResource)
```

What it does:

1. Reads the current value of the `Value`.
2. Constructs the target `R` from it via `R::new(init_value)`.
3. Subscribes (`add_event`) so every subsequent `set` on the `Value` is pushed
   into the target via `R::set(...)`.
4. Returns the target plus a [`DropResource`]crate::DropResource. **The subscription lives only as
   long as that `DropResource` is held** — drop it and the mirroring stops.

The same pattern exists on the resource-fetching types:

- [`LazyCache<T>::synchronize`]crate::LazyCache::synchronize mirrors the cache's current `Resource<Rc<T>>` into
  a target of type [`ValueSynchronize`]crate::ValueSynchronize`<Rc<T>>`. Loading / Error / Uninitialized
  states are normalized to `T::default()` (hence the extra `T: Default + Clone`
  bound), so the target always sees a concrete `Rc<T>`.
- Internally this is implemented by `CacheValue<T>::synchronize`, which performs
  the same normalize-and-subscribe dance.

So `synchronize` is *one* mechanism with several entry points ([`Value`](crate::Value),
[`LazyCache`](crate::LazyCache), `CacheValue`), all parameterized over the target type `R`.

---

## `ValueSynchronize` trait

```rust,ignore
pub trait ValueSynchronize<T: PartialEq + Clone + 'static>: Sized {
    fn new(value: T) -> Self;
    fn set(&self, value: T);
}
```

This is the contract a type must satisfy to be a valid [`synchronize`](crate::Value::synchronize) *target*:

- `new(value)` — build the target from the source's initial value.
- `set(value)` — accept each subsequent update.

Note `set` takes `&self` (not `&mut self`): synchronization targets are expected
to use interior reactivity ([`Value`](crate::Value), `Rc`, etc.) internally, so a shared handle
can absorb updates. That is exactly why targets must also be `Clone` — the
closure registered with `add_event` captures a clone of the target.

The only target implemented in-tree today is `Collection<T>`, but the trait is
public so you can write your own (e.g. a target that maintains an index, a
running total, or a sorted view).

---

## `CollectionKey`

```rust,ignore
pub trait CollectionKey {
    type Key: Eq + Hash + Clone + std::fmt::Debug + 'static;
    type Value: Clone + PartialEq + 'static;
    fn get_key(val: &Self::Value) -> Self::Key;
}
```

[`CollectionKey`](crate::CollectionKey) is a **marker / descriptor trait**. You implement it on a
zero-sized marker type (not on the item itself), and it declares three things:

- `Value` — the item type stored in the list.
- `Key` — a stable identity for an item (e.g. a database id).
- `get_key` — how to extract the key from an item.

Typical implementation:

```rust,ignore
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Item { pub id: u32, pub name: String }

pub struct ItemKey; // marker type

impl CollectionKey for ItemKey {
    type Key = u32;
    type Value = Item;
    fn get_key(val: &Item) -> u32 { val.id }
}
```

The marker type (`ItemKey`) is the generic parameter `T` threaded through
`Collection<T>`, `CollectionModel<T>`, [`LazyListCache<T>`](crate::LazyListCache), and the memoized list
renderers. Keying matters for two reasons:

1. **Memoization** — items keep the *same* per-item `Value`/`Computed` across
   updates as long as their key is stable. Only items whose content actually
   changed cause a re-render.
2. **Deduplication**`Collection` logs an error and skips items with a
   duplicate key within a single list.

---

## How `Collection<T>` ties it together

`Collection<T>` is the internal structure produced by `synchronize`. It is *not*
exported at the crate root, but understanding it explains the whole flow:

- It holds an `ItemDataCollection<T>` — a `HashMap<Key, (Value<V>, Computed<V>)>`
  — plus an `order: Value<Vec<CollectionModel<T>>>` describing the current list
  order.
- On `set(new_list)` it:
  - extracts the key of each item,
  - reuses the existing per-item `Value` if the key is already known (calling
    `value.set(item)`, which only triggers downstream work when the item is
    actually `!=` the previous one),
  - creates a fresh per-item `Value`/`Computed` for new keys,
  - updates `order`,
  - retains only the keys still present (dropping per-item state for removed
    items).
- It implements `ValueSynchronize<Rc<Vec<T::Value>>>`, which is what makes it a
  legal `synchronize` target.

`Collection::get()` returns `Computed<Vec<CollectionModel<T>>>`, where each
`CollectionModel<T>` is `{ key: T::Key, model: Computed<T::Value> }`. The
*outer* computed changes when the list's order/membership changes; each *inner*
`model` computed changes only when that specific item changes. This two-level
reactivity is the source of the memoization.

---

## `render_list_memo`

```rust,ignore
pub fn render_list_memo<T: CollectionKey + 'static>(
    value: &Value<Rc<Vec<T::Value>>>,
    render: impl Fn(&Computed<T::Value>) -> DomNode + 'static,
) -> DomNode
```

Renders a reactive list from a `Value<Rc<Vec<Item>>>`, memoizing each item.

Internally it:

1. calls `value.synchronize::<Collection<T>>()` to obtain the keyed collection
   and its `DropResource`,
2. feeds `collection.get()` into the lower-level `render_list`, using each
   item's `key` for identity and rendering each item from its **own per-item
   `Computed`**,
3. attaches the synchronization `DropResource` (and a handle on the source
   `value`) to the resulting node so everything is cleaned up when the node is
   dropped.

Because the render closure receives a `&`[`Computed`](crate::Computed)`<T::Value>` (not a bare value),
the rendered subtree for an item re-runs only when that item changes — not when
sibling items or the list order change.

Use this when **your list already lives in a [`Value`](crate::Value)** that you own.

---

## `render_resource_list_memo`

```rust,ignore
pub fn render_resource_list_memo<T: CollectionKey + 'static>(
    value: &LazyCache<Vec<T::Value>>,
    render: impl Fn(&Computed<T::Value>) -> DomNode + 'static,
) -> DomNode
```

Identical in shape to [`render_list_memo`](crate::render::render_list_memo), but the source is a
[`LazyCache`](crate::LazyCache)`<Vec<Item>>` — a lazily-loaded, possibly remote resource that
auto-refreshes on a TTL. It calls `value.synchronize::<Collection<T>>()` on the
`LazyCache` (which normalizes Loading/Error to an empty/`default` list) and then
renders exactly like `render_list_memo`.

Use this when **your list comes from a fetched resource** and you still want
per-item memoization.

> Related: [`LazyListCache<T>`]crate::LazyListCache is a higher-level wrapper (also keyed by
> [`CollectionKey`]crate::CollectionKey) that adds optimistic create/update/delete and per-item
> fetching on top of a list resource. `render_resource_list_memo` is the
> lower-level renderer for a plain `LazyCache<Vec<Item>>`. See the
> [`LazyListCache` guide]crate::guides::lazy_list_cache.

---

## End-to-end example

```rust,ignore
use std::rc::Rc;
use vertigo::{dom, CollectionKey, Computed, DomNode, Value};
use vertigo::render::render_list_memo; // path-dependent; see "Public surface"

#[derive(Clone, PartialEq, Eq, Debug)]
struct Item { id: u32, name: String }

struct ItemKey;
impl CollectionKey for ItemKey {
    type Key = u32;
    type Value = Item;
    fn get_key(v: &Item) -> u32 { v.id }
}

fn view(items: &Value<Rc<Vec<Item>>>) -> DomNode {
    render_list_memo::<ItemKey>(items, |item: &Computed<Item>| {
        let item = item.clone();
        item.render_value(|it| dom! { <div>{it.name}</div> })
    })
}
```

When `items` is updated:

- items with unchanged content are **not** re-rendered (their inner `Computed`
  did not change),
- only added / removed / reordered / mutated items cause DOM work.

---

## Public surface (what you can use directly)

| Item                          | Exported as                              |
| ----------------------------- | ---------------------------------------- |
| [`Value::synchronize`]crate::Value::synchronize | method on [`vertigo::Value`]crate::Value |
| [`LazyCache::synchronize`]crate::LazyCache::synchronize | method on [`vertigo::LazyCache`]crate::LazyCache |
| [`ValueSynchronize`]crate::ValueSynchronize | `vertigo::ValueSynchronize`              |
| [`CollectionKey`]crate::CollectionKey | `vertigo::CollectionKey`                 |
| [`render_list_memo`]crate::render::render_list_memo | `vertigo::render::render_list_memo`      |
| [`render_resource_list_memo`]crate::render::render_resource_list_memo | `vertigo::render::render_resource_list_memo` |
| `Collection`, `CollectionModel` | `vertigo::render::collection::*` (lower-level) |

[`render_list_memo`](crate::render::render_list_memo) / [`render_resource_list_memo`](crate::render::render_resource_list_memo) are reachable through the public
`render` module (`vertigo::render::…`); they are not re-exported at the crate
root the way [`CollectionKey`](crate::CollectionKey) is.