Phoenix1.7 core_components.exの外に関数コンポーネントを作る

こんにちは！
プログラミング未経験文系出身、Elixirの国に迷い込んだ？！見習いアルケミストのaliceと申します。
今回はcore_components.exの外に関数コンポーネントを作ってみました。

■「Phoenix1.7 関数コンポーネントで遊んでみる」シリーズの目次
① [モーダル上に文字数制限無しのtextareaを作成する](https://qiita.com/Alicesky2127/items/f47a236e1d10897a29b0)
|> ②[Phoenix.Component.attr/3 を使って関数コンポーネントを作る](https://qiita.com/Alicesky2127/items/55e97240ba9a97ae2ec8)
|> ③[core_components.exの外に関数コンポーネントを作る](https://qiita.com/Alicesky2127/items/c9d08c37bcc628e938f3)
|> ④[自作した関数コンポーネントを<.関数名>で呼び出す](https://qiita.com/Alicesky2127/items/0c111b8ee1fe547d20ff)


# 目的
前回作ったtextareaをレンダリングする関数コンポーネント`input(%{type: "textarea"} = assigns)`について、core_components.exの外に別モジュールとして作り、そちらを呼び出すように切り替えたい。
なお、新しいモジュールを置く場所と名前は`lib/contact_web/components/my_components.ex`とする。


前回の記事はこちら

https://qiita.com/Alicesky2127/items/55e97240ba9a97ae2ec8


## 実行環境
Windows 11 ＋ WSL2 ＋ Ubuntu 22.04
Elixir v1.14.3
Phoenix v1.7.3

# 仮説
- my_components.ex
└`use Phoenix.Component`を記述するのでは？
└`core_components.ex`内`input(%{type: "textarea"} = assigns)`を真似して作成するのでは？

- form_component.ex
└呼び出す関数の切り替えが必要では？

# 結果
以下のように実施して目的を達成しました。

## 1.my_components.ex内にattr/3とslot/3を移植する[^1]

```diff_elixir:my_components.ex
+ defmodule ContactWeb.Mycomponents do
+   use Phoenix.Component

+  attr :id, :any, default: nil
+  attr :name, :any
+  attr :label, :string, default: nil
+  attr :value, :any
+  attr :rows, :integer, default: 2

+  attr :type, :string,
+    default: "text",
+    values: ~w(checkbox color date datetime-local email file hidden month number password
+               range radio search select tel text textarea time url week)

+  attr :field, Phoenix.HTML.FormField,
+    doc: "a form field struct retrieved from the form, for example: @form[:email]"

+  attr :errors, :list, default: []
+  attr :checked, :boolean, doc: "the checked flag for checkbox inputs"
+  attr :prompt, :string, default: nil, doc: "the prompt for select inputs"
+  attr :options, :list, doc: "the options to pass to Phoenix.HTML.Form.options_for_select/2"
+  attr :multiple, :boolean, default: false, doc: "the multiple flag for select inputs"

+  attr :rest, :global,
+    include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
+                multiple pattern placeholder readonly required rows size step)

+  slot :inner_block
```


## 2. my_components.ex内にinput/1の共通処理部分を移植する
これはまだコピペしただけです。手順3のaliasを書かないとエラーになります。
```diff_elixir:my_components.ex
+ def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
+    assigns
+    |> assign(field: nil, id: assigns.id || field.id)
+    |> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
+    |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
+    |> assign_new(:value, fn -> field.value end)
+    |> input()
+ end
```

## 3. core_components.exおよびmy_components.exのaliasを書く
`my_components.ex`内の`input/1`と、`core_components.ex`内の`input/1`が呼び分けできるように`my_components.ex`および`core_components.ex`のaliasを書きます。

```diff_elixir:my_components.ex
 defmodule ContactWeb.Mycomponents do
   use Phoenix.Component
+  alias ContactWeb.CoreComponents
+  alias ContactWeb.Mycomponents
```

aliasを書いたので以降は「モジュール名.関数名」で呼び出します。
```diff_elixir:my_components.ex
  def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
     assigns
     |> assign(field: nil, id: assigns.id || field.id)
-    |> assign(:errors, Enum.map(field.errors, &translate_error(&1)))
+    |> assign(:errors, Enum.map(field.errors, &CoreComponents.translate_error(&1)))
     |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> "[]", else: field.name end)
     |> assign_new(:value, fn -> field.value end)
-    |> input()
+    |> Mycomponents.input()
  end
```

## 4. my_components.ex内にinput(%{type: "textarea"} = assigns)を移植し、手順3を適用する
core_components.ex内のinput(%{type: "textarea"} = assigns)とUI上で見分けがつくように、CSSプロパティに`bg-green-500/50`[^2]を指定しています。
手順3の通り、core_components.exの関数は「モジュール名.関数名」で呼び出します。

```diff_elixir:my_components.ex
+ def input(%{type: "textarea"} = assigns) do
+    ~H"""
+    <div phx-feedback-for={@name}>
+      <CoreComponents.label for={@id}><%= @label %></CoreComponents.label>
+      <textarea
+        id={@id}
+        name={@name}
+        rows={@rows}
+        class={[
+          "mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6",
+          "phx-no-feedback:border-zinc-300 phx-no-feedback:focus:border-zinc-400 bg-green-500/50",
+          @errors == [] && "border-zinc-300 focus:border-zinc-400",
+          @errors != [] && "border-rose-400 focus:border-rose-400"
+        ]}
+        {@rest}
+      ><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
+      <CoreComponents.error :for={msg <- @errors}><%= msg %></CoreComponents.error>
+    </div>
+    """
+  end
```

## 5. form_component.exで呼び出す関数を作成したものに切り替える
```diff_elixir:form_component.ex
def render(assigns) do
    ~H"""
    <div>
      <.header>
        <%= @title %>
        <:subtitle>Use this form to manage comment records in your database.</:subtitle>
      </.header>

      <.simple_form
        for={@form}
        id="comment-form"
        phx-target={@myself}
        phx-change="validate"
        phx-submit="save"
      >
        <.input field={@form[:name]} type="text" label="Name" />
-       <.input field={@form[:message]} type="textarea" label="Message" />
+       <Mycomponents.input field={@form[:message]} type="textarea" label="Message" />
        <:actions>
          <.button phx-disable-with="Saving...">Save Comment</.button>
        </:actions>
      </.simple_form>
    </div>
    """
  end
```

# できました(^▽^)/
背景色が`bg-green-500/50`のtextareaが反映されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677280/4a0ce485-0996-f64d-6184-55f117582fe0.png)



# ～Elixirの国のご案内～
↓Elixirって何ぞや？と思ったらこちらもどぞ。Elixirは先端のアレコレをだいたい全部できちゃいます:laughing::sparkles::sparkles:

https://qiita.com/piacerex/items/09876caa1e17169ec5e1

↓ゼロからElixirを始めるなら「エリクサーチ」がおすすめ！私もエンジニア未経験から学習中です。

https://elixir-lang.info/topics/entry_column


↓[We Are The Alchemists, my friends!:bouquet:](https://qiita.com/torifukukaiou/items/6bcd5bbb80df9e594e9e "Awesome-san's Awesome article")[^10]
Elixirコミュニティは本当に優しくて温かい人たちばかり！
私が挫折せずにいられるのもこの恵まれた環境のおかげです。
まずは気軽にコミュニティを訪れてみてください。[^11]


https://speakerdeck.com/elijo/elixirkomiyunitei-falsebu-kifang-guo-nei-onrainbian


[^1]:厳密には使用していないattr/3（第一引数が:checkedなど)およびslot/3は必要無いのですが、ひとまず全部前回の記事から移植してきています。

[^2]: https://tailwindcss.com/docs/background-color

[^10]: @torifukukaiouさんのAwesomeな名言をお借りしました。Elixirコミュニティを一言で表すと、これに尽きます。


[^11]: @kn339264さんの素敵なスライドをお借りしました。Elixirコミュニティはいろんな形で活動中！