LiveComponentについて

- [LiveComponentとは](#livecomponentとは)
  - [定義](#定義)
    - [注意](#注意)
  - [コンポーネントの呼び出し](#コンポーネントの呼び出し)
    - [ネスト](#ネスト)
  - [使用例](#使用例)
  - [LiveComponentの独立したライフサイクル](#livecomponentの独立したライフサイクル)
  - [結論と感想](#結論と感想)
  - [よくある問題](#よくある問題)
    - [LiveComponentでflashメッセージを表示できない](#livecomponentでflashメッセージを表示できない)

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveComponent.html

# LiveComponentとは

独自のコンポーネントの定義には**コアコンポーネント**(CoreComponents)に記述したり、
[関数コンポーネント](https://qiita.com/B_tanuki/items/5086a4c6277d16813e0d)などを作成したりする。

LiveViewでは<font color="red">LiveComponent</font>を定義することができる。

このLiveComponentは、構成に必要な<font color="red">データが更新されるごとにコンポーネントを更新</font>してくれるメリットがある。

## 定義

LiveComponentは、次の2つの要点を考慮して記述を行う。

* `use ..., :live_component`の記述
* `render()`の定義
* `update()`の定義

`use ..., :live_component`はLiveComponentを作成するための必要な記述である。

LiveComponentはマウントされた際に、次の順に関数を呼び出す。

1. `mount()`
2. `update()`
3. `render()`

`mount()`はマウントされたときに実行される。
`update()`はマウント後、または更新時に実行される。
`render()`は`update()`の実行後に実行される。

よって<font color="red">最低限必要になるのは、`update()`と`render()`の定義</font>ということになる。

LiveComponentを作成するための最低限のフォーマットは以下のようになる。

```elixir
def ライブコンポーネント名 do
  use 省略Web, :live_component

  @impl true
  def render(assigns) do
    ~H"""
    ここにHTMLを記述。
    """
  end

  @impl true
  def update(assigns, socket) do
    {:ok, 
      socket 
      |> assign(assigns)
    }
  end
end
```

また、`handle_event()`を追加してイベントハンドリングを行うこともできる。

### 注意

  作成したLiveComponentに以下のボタンが存在するものとする。

```elixir
<button phx-click="sample">Click</button>
```

このままでは上記の要素をクリックしたとき、イベントは
<font color="red">LiveComponentの呼び出し元に送信</font>されてしまう。

イベントがLiveComponentに送信されるには、以下の記述を追加して<font color="red">イベントの送信先を指定する</font>必要がある。

```elixir
phx-target={@myself}
```

また、`phx-target`を使用することで、別のLiveComponentにイベントを送信することもできる。

## コンポーネントの呼び出し

LiveComponentを呼び出す際は、次のフォーマットに基づいて記述を行うと良い。

```elixir
<.live_component
  module={LiveComponentモジュールを指定}
  id={idを指定}
  # ここからはオプション
  データ名=[データ]
/>
```

呼び出し元からデータを受け取って表示する場合、以下のような記述を行う。

```elixir
<.live_component
  ...
  name={@name} # socket.assigns.nameを渡している
  list={@list} # socket.assigns.listを渡している
/>
```

### ネスト

LiveComponentはタグをネストすることができる。

ネストしたタグは以下のように記述して表示することができる。

```elixir
<%= render_slot(@inner_block) %>
```

## 使用例

使用例から、データの更新を自動で反映する具体的な動作を確認する。

Liveモジュールに、以下が記述されているものとする。

```elixir
...
  def mount(_params, _session, socket) do
    {:noreyply,
      socket
      |> assign(:selected, nil)
    }
  end

  def handle_event("button_click", %{"name" => name}, socket) do
    {:noreply,
      socket
      |> assign(:selected, name)
    }
  end
```

heexテンプレートには、以下の要素が含まれているものとする。

```heex
<button phx-click="button_click" phx-value-name="A">Click A</button>
<button phx-click="button_click" phx-value-name="B">Click B</button>
```

LiveComponentには、以下が記述されているものとする。

```elixir
def render(assigns) do
  ~H"""
    <%= if ls_nil(@selected) do %>
      <p>未選択</p>
    <% else %>
      <p><%= "#{@name}を選択中!" %>
    <% end %>
  """
end

def update(assigns, socket) do
  {:ok, 
    socket
    |> assign(assigns)
  }
end
```

ボタンをクリックすることで、**マウント時に追加されたデータ**(selected)が更新される。

この際、LiveComponentが更新され、<font color="red">データの更新を反映する</font>ことができる。

## LiveComponentの独立したライフサイクル

LiveComponent内でデータを更新しても、**呼び出し元のLiveViewのデータには反映されない**。

なぜなら、<font color="red">LiveComponentはLiveViewから独立したライフサイクルを持つ</font>からである。

例として、LiveViewのデータに`selected: nil`というデータが存在するものとする。
テンプレートには、以下が記述されているものとする。

```heex
<%= if @selected do %>
  <%= @selected %>
<% end %>

<.live_component
  module={SampleButton}
  selected={@selected}
/>
```

LiveComponentには以下が定義されているものとする。

```elixir
defmodule SampleButton do
  ...

  def render(assigns) do
    ~H"""
    <button phx-click="select" phx-target={@myself} phx-value-name="apple">Click!</button>
    """
  end

  def update(assigns, socket) do
    {:ok, 
      socket
      |> assign(assigns)  
    }
  end

  def handle_event("select", %{"name" => name}, socket) do
    {:noreply, 
      socket
      |> assign(:selected, name)
    }
  end
```

このような場合、`select`イベントが処理されても<font color="red">LiveViewのデータに影響を与えない</font >ため、
`selected`の内容がテンプレート内に展開されることはない。

LiveComponentからLiveViewのデータを更新したい場合、**イベントの処理をLiveView側で行うとよい**。

## 結論と感想

LiveComponentを使用することで、データの更新を反映するコンポーネントを記述
することができる。

ユーザの入力によって表示内容が変更されるコンポーネントは、
積極的にLiveComponentを使用すると良いと思った。

具体的にはメニュー(サブメニュー)、フォームの切り替えはLiveComponentを使用して実装したいと思った。

## よくある問題

### LiveComponentでflashメッセージを表示できない

この問題はLiveViewとLiveComponentは<font color="red">**異なるsocketを持っている**</font>ために発生する問題である。

LiveComponentのイベント処理でflashメッセージを表示させたい場合、**LiveViewにメッセージを送信する**ことで問題は解決する。


```elixir
# LiveComponent
send self(), {:display_flash, {:info, "Flashメッセージ"}}

# LiveView
def handle_info({:display_flash, {:info, flash_message}}, socket) do
  {:noreply,
    socket
    |> put_flash(:info, flash_message)
  }
end
```

個人的には、特定のLiveComponentからのflashメッセージの表示であることをわかりやすくする場合、以下のように記述している。

ここではFileContentViewerというLiveComponentからflashメッセージを表示する例とする。

```elixir
def handle_info({:display_flash_from_file_content_viewer, {:info, flash_message}}, socket) do
    {:noreply,
        socket
        |> put_flash(:info, flash_message)
    }
end
```