メインコンテンツまでスキップ

HalogenでTextInputをコンポーネント化する

· 約6分

ReactTextInputを使う時は、入力内容をonChangeで取得し、それを状態として保持するコンポーネントを使います。

Halogenでも同じようなコンポーネントがあると便利です。

ここでは以下の機能を備えたTextInputコンポーネントを作成します。

TextInputの概要
  • 入力内容を状態で保持している
  • 親コンポーネントからはQueryTextInputの入力内容を取得する
  • TextInputのスタイルは外部から設定できるようにする

ではコンポーネントを作成するために必要なものを順次みていきましょう。

TextInputの入力

親コンポーネントから渡されるプロパティです。

type Input = { value :: String
, style :: Maybe CSS }

valueというのがTextInputの初期値です。styleCSSです。

TextInputの状態

TextInputの状態は親コンポーネントからの入力と同じものを保持します。TextInputに何か入力される度にvalueが更新されます。styleは初期値から更新されることはありません。

type State = { value :: String
, style :: Maybe CSS }

TextInputのアクション

TextInputに何か入力された時のアクションです。onChangeで捕捉します。

data Action = OnChange Event

TextInputのクエリ

親コンポーネントから呼ばれるクエリです。親コンポーネントがTextInputの状態に入っているvalueを取得します。親コンポーネントのボタンが押下されたときなどに呼ばれることを想定しています。

data Query a = GetValue (String -> a)

TextInputの状態の初期値

TextInputの状態の初期値は、TextInputInputと同じです。

initialState :: Input -> State
initialState input = input

TextInputのビュー

TextInputのビューは単純にHTMLInputElementです。onChange時に先程定義したOnChangeアクションを発行します。

render :: State -> H.ComponentHTML Action () m
render state = HH.input
$ [ style state.style
, HP.type_ HP.InputText
, HP.value state.value
, HE.onChange OnChange ]
<> case state.style of
Just s -> [style s]
_ -> []

TextInputのActionの処理

TextInputのActionの処理は、onChange時に発行されたOnChangeアクションを受け取って、EventtargetであるHTMLInputElementvalueで状態を更新します。

handleAction :: Action -> H.HalogenM State Action () output m Unit
handleAction (OnChange e) = do
state <- H.get
value <- H.liftEffect $ getInputText e
H.put state { value = value }
pure unit

getInputText :: Event.Event -> Effect String
getInputText e = e
# Event.target
>>= HTMLInputElement.fromEventTarget
# case _ of
Just a -> HTMLInputElement.value a
_ -> throw "Failed to convert a HTMLInputElement"

TextInputのQueryの処理

親コンポーネントからクエリを受け取った時の処理です。状態として保持しているvalueを親コンポーネントに返します。

    handleQuery :: forall a. (Query a) -> H.HalogenM State Action () output m (Maybe a)
handleQuery (GetValue reply) = do
state <- H.get
pure $ Just $ reply state.value

TextInputコンポーネントの定義

上記で定義したものをまとめてtextInputコンポーネントを作成します。

textInput :: forall output m. MonadEffect m => H.Component Query Input output m
textInput = H.mkComponent
{ initialState
, render
, eval: H.mkEval $ H.defaultEval { handleAction = handleAction
, handleQuery = handleQuery } }

TextInputのSlot

親コンポーネントで使用することになるTextInputSlotの定義です。TextInputOutputがないのでVoidにしています。slotId**は親コンポーネントで定義することにします。

type Slot slotId = H.Slot Query Void slotId

親コンポーネントで使用する

上記で定義したTextInputコンポーネントを親から使用する例です。

子コンポーネントをビュー内に入れるにはslot/slot_を使用します。ここではTextInputコンポーネントはOutputがないのでslot_を使っています。

ここではTextInputコンポーネントを複数使う場合のためにslotIdの型をIntにしていますが、もっと分かりやすくStringにしてもよいと思います。Ordクラスのインスタンスであれば何でも使用できます。

type Slots = ( textField :: Slot Int )
textInput_ = Proxy::Proxy "textInput"

slot_render関数の中で使用します。HTMLの中にコンポーネントを埋め込むためにはslot_で囲んであげる必要があります。

第2引数のslotIdIntにしたので、ここでは0としています。

第3引数にはTextInputコンポーネントを入れます。

第4引数である子コンポーネントの入力値としてTextInputInputの値を入れます。ここではHTMLInputElementvalueの初期値は""にしています。

HH.slot_ textInput_ 0 textInput { value: "", style: Nothing }

以下は親コンポーネントでTextInputの値が必要になったときに、その値を取得する方法です。親コンポーネントのhandleAction等で呼ばれることを想定しています。

value <- H.request textField_ 0 GetValue