# `Cinder.Update`
[🔗](https://github.com/sevenseacat/cinder/blob/v0.18.0/lib/cinder/update.ex#L5)

Efficient in-memory updates for Cinder collection data.

This module provides functions to update or remove individual items in a
collection's data without triggering a full database re-query. This is useful
for applying small changes received via PubSub (e.g., status changes, counter
increments, or deletions) where re-fetching all 25+ items would be wasteful.

## Usage

    # Update a single item by ID
    def handle_info({:user_status_changed, user_id, new_status}, socket) do
      {:noreply, update_item(socket, "users-table", user_id, fn user ->
        %{user | status: new_status}
      end)}
    end

    # Update multiple items with the same function
    def handle_info({:users_activated, user_ids}, socket) do
      {:noreply, update_items(socket, "users-table", user_ids, fn user ->
        %{user | active: true}
      end)}
    end

    # Remove a deleted item and drop it from the selection
    def handle_info({:user_deleted, user_id}, socket) do
      {:noreply, remove_item(socket, "users-table", user_id)}
    end

    # Only update if the item is currently visible (no-op otherwise)
    def handle_info({:user_updated, user_id, changes}, socket) do
      {:noreply, update_if_visible(socket, "users-table", user_id, fn user ->
        Map.merge(user, changes)
      end)}
    end

## Caveats

- These functions modify in-memory data only. Computed fields, aggregates,
  and calculations that come from the database will NOT be recalculated.
- Removing an item does not backfill its page: rows from the next page move up on
  the next refresh. A removal that empties the page reads it again.
- For changes that affect derived data, ordering or filtering, use `refresh_table/2`
  instead.
- If the item is not found in the current data, the update is silently ignored.
- The `update_if_visible` functions check visibility within the component itself.

# `remove_item`

Removes a single item from a collection by its ID.

The item is removed from the currently rendered data without a database query.
If it was selected, its ID is also removed from the collection's selection and
`on_selection_change` is notified with `action: :remove`. The pagination footer
follows along: the row leaves `page.results` and the total count drops by the
number of rows actually removed.

Rows from the next page do not move up to fill the gap, and computed fields,
aggregates and ordering are not recalculated. Follow it with `refresh_table/2`
when those must be reconciled. If the removal empties the page, the collection
reads that page again (or the previous one, if the emptied page was the last).

## Example

    def handle_info({:user_deleted, user_id}, socket) do
      {:noreply, remove_item(socket, "users-table", user_id)}
    end

# `remove_items`

Removes multiple items from a collection by their IDs.

This is the batch equivalent of `remove_item/3`. IDs not present on the current
page are ignored for rendered data and the total count, but are still removed
from the collection's selection, which may span pages.

## Example

    def handle_info({:users_deleted, user_ids}, socket) do
      {:noreply, remove_items(socket, "users-table", user_ids)}
    end

# `update_if_visible`

Updates an item only if it's currently visible in the collection.

This combines the efficiency of `update_item/4` with visibility checking.
The component itself checks if the item exists in its current data before
applying the update. If the item is not visible, the update function is never
called, enabling lazy loading patterns.

You can pass either an ID or a raw item (struct/map with the ID field). When
passing a raw item, that item is passed to your update function instead of
the existing table data - useful for lazy loading scenarios where you want
to transform incoming PubSub data.

Safe to call when you're unsure if the item is currently displayed.

## Parameters

- `socket` - The LiveView socket
- `collection_id` - The ID of the collection (string)
- `id_or_item` - The ID of the item to update, or a raw item (map/struct with ID field)
- `update_fn` - A function that receives the item and returns the updated item

## Returns

The socket (unchanged, but update message has been sent to component).

## Examples

    # Simple update - pass ID, receive existing item from table
    def handle_info({:user_typing, user_id}, socket) do
      {:noreply, update_if_visible(socket, "users-table", user_id, fn user ->
        %{user | typing: true}
      end)}
    end

    # Lazy loading - pass raw item, receive it back for transformation
    def handle_info({:user_updated, raw_user}, socket) do
      {:noreply, update_if_visible(socket, "users-table", raw_user, fn raw ->
        {:ok, loaded} = Ash.load(raw, [:profile, :settings])
        loaded
      end)}
    end

# `update_item`

Updates a single item in a collection by its ID.

Applies the given function to the item matching the ID. If no item matches,
the data remains unchanged.

## Parameters

- `socket` - The LiveView socket
- `collection_id` - The ID of the collection (string)
- `id` - The ID of the item to update
- `update_fn` - A function that receives the item and returns the updated item

## Returns

The socket (unchanged, but update message has been sent).

## Examples

    # Update user's online status
    update_item(socket, "users-table", user_id, fn user ->
      %{user | online: true}
    end)

    # Increment a counter
    update_item(socket, "posts-table", post_id, fn post ->
      %{post | view_count: post.view_count + 1}
    end)

# `update_items`

Updates multiple items in a collection by their IDs.

Applies the given function to all items whose IDs are in the provided list.
Items not in the list are left unchanged.

## Parameters

- `socket` - The LiveView socket
- `collection_id` - The ID of the collection (string)
- `ids` - List of IDs of items to update
- `update_fn` - A function that receives each item and returns the updated item

## Returns

The socket (unchanged, but update message has been sent).

## Examples

    # Mark multiple users as active
    update_items(socket, "users-table", user_ids, fn user ->
      %{user | active: true}
    end)

    # Apply discount to selected products
    update_items(socket, "products-table", product_ids, fn product ->
      %{product | price: product.price * 0.9}
    end)

# `update_items_if_visible`

Updates multiple items only if any are currently visible.

Like `update_if_visible/4` but for multiple IDs. Only items that are both
in the provided list AND currently visible will be updated. The component
itself determines which items are visible by checking its current data.

The update function is called ONCE with ALL visible items, enabling efficient
batch operations. If no items are visible, the function is never called.

Also accepts a single struct for convenience - it will be wrapped in a list.

## Parameters

- `socket` - The LiveView socket
- `collection_id` - The ID of the collection (string)
- `ids_or_items` - List of IDs, list of raw items, or a single struct
- `update_fn` - A function that receives a list of visible items and returns
  either a list of updated items or a map of `%{id => updated_item}`

## Returns

The socket (unchanged, but update message has been sent to component).

## Examples

    # Simple batch update
    def handle_info({:users_went_offline, user_ids}, socket) do
      {:noreply, update_items_if_visible(socket, "users-table", user_ids, fn users ->
        Enum.map(users, &%{&1 | online: false})
      end)}
    end

    # Lazy batch loading - only loads visible items
    def handle_info(%{payload: %{data: data}}, socket) do
      {:noreply, update_items_if_visible(socket, "table", List.wrap(data), fn visible_items ->
        Ash.load!(visible_items, [:relations])
      end)}
    end

---

*Consult [api-reference.md](api-reference.md) for complete listing*
