README.md

# LiveBulkAsync

[![Hex](https://img.shields.io/hexpm/v/live_bulk_async.svg)](https://hex.pm/packages/live_bulk_async)
[![Hexdocs](https://img.shields.io/badge/-docs-green)](https://hexdocs.pm/live_bulk_async)

LiveBulkAsync is a small library that extends LiveView's async support to work with LiveComponent's `update_many` function.

## Installation

First add `LiveBulkAsync` to your list of dependencies in `mix.exs`:

``` elixir
def deps do
  [
    {:live_bulk_async, "~> 0.1.0"}
  ]
end
```

Now you can directly add support for it in a specific component:

``` elixir
defmodule MyComponent do
  use Phoenix.LiveComponent

  # Add this
  import LiveBulkAsync
  
  ...
end
```

Or you can enable it in all your components by adding it to your `Web` module `live_component` function:

``` elixir
def live_component do
  quote do
    ...
    
    # Add this
    import LiveBulkAsync
  end
end
```

## Usage

Now inside your component `update_many` function, you can use it like this for `start_many_async`:

``` elixir
defmodule MyComponent do
  @moduledoc false

  use Phoenix.LiveComponent

  def update_many(assigns_and_sockets) do
    assigns_and_sockets
    |> Enum.map(fn {assigns, socket} ->
      socket |> assign(assigns) |> assign(loading?: true)
    end)
    |> start_many_async(:content, &load/0)
  end

  def handle_async(:content, {:ok, content}, socket) do
    {:noreply, assign(socket, loading?: false, content: content)}
  end

  def handle_async(:content, {:exit, reason}, socket) do
    {:noreply, socket}
  end

  def render(assigns) do
    ~H"""
    <div id={@id}>
      <div :if={@loading?}>loading...</div>
      <div :if={not @loading?}>{@content}</div>
    </div>
    """
  end

  defp load! do
    # Load something here
    ["content 1", "content 2"]
  end
end
```

And like this for `assing_many_async`:

``` elixir
defmodule MyComponent do
  @moduledoc false

  use Phoenix.LiveComponent

  def update_many(assigns_and_sockets) do
    assigns_and_sockets
    |> Enum.map(fn {assigns, socket} ->
      socket |> assign(assigns) |> assign(loading?: true)
    end)
    |> assign_many_async(:content, fn -> load!() end)
  end

  def render(assigns) do
    ~H"""
    <div id={@id}>
      <.async_result :let={content} assign={@content}>
      <:loading>Loading...</:loading>
      <:failed :let={reason}><pre>{inspect(reason)}</pre></:failed>
        <div>{content}</div>
      </.async_result>
    </div>
    """
  end

  defp load! do
    # Load something here
    {:ok, %{content: ["content 1", "content 2"]}}
  end
end
```

## Cancel running task

You can also cancel an already running task using `cancel_many_async`, just keep in mind that calling it will cancel the task for all the components that are using it, not only the one you called it from.

You can cancel a task directly from the `update_many` call like this:

``` elixir
def update_many(assigns_and_sockets) do
    assigns_and_sockets
    |> Enum.map(fn {assigns, socket} -> socket end)
    |> cancel_many_async(:content)
end
```

Or from any other callback inside the component that has access to the socket:

``` elixir
def handle_event("cancel_load", _params, socket) do
  socket = cancel_many_async(socket, :content)

  {:noreply, socket}
end
```