Help us understand the problem. What is going on with this article?

Reason-ReactでFormを扱うときにハマったこと

More than 1 year has passed since last update.

Reason-React

https://reasonml.github.io/reason-react/
ReasonMLでReactを利用することができるのですが、通常のReactの感覚でFormを扱おうとしたら、いくらかハマったので、それをまとめました。

Formを操作するサンプル

完成形はこちらです。
Beltを使っているので、こちらの記事も参考にしてください。
https://qiita.com/soebosi/items/404e035b74636ee8b406

open Belt;                                                                                                                                                                                                                                    

type state = {
  name: string,
  nameArray: array(string),
};
type action =
  | AddName(string)
  | ChangeText(string);

let s_ = ReasonReact.stringToElement;
let a_ = ReasonReact.arrayToElement;

let component = ReasonReact.reducerComponent("TopPage");
let make = (_children) => {
  let handleChange = (e, self) => {
    let target = ReactEventRe.Form.target(e);
    let name = ReactDOMRe.domElementToObj(target)##value;
    self.ReasonReact.send(ChangeText(name));
  };  
  let handleSubmit = (e, self) => {
    ReactEventRe.Form.preventDefault(e);
    self.ReasonReact.send(AddName(self.state.name));
    self.ReasonReact.send(ChangeText(""));
  };  
  {
  ...component,
  initialState: () => {
    {   
      name: "", 
      nameArray: [||],
    };  
  },
  reducer: (action, state) => {
    switch(action) {
    | ChangeText(name) => ReasonReact.Update({...state, name})
    | AddName(name) => {
      let nameArray = Array.concat(state.nameArray, [|name|])
        |. SortArray.stableSortBy(Pervasives.compare);
      ReasonReact.Update({...state, nameArray});
    }   
    }   
  },  
  render: self =>
    <div>
      <form onSubmit={self.handle(handleSubmit)}>
        <label>
          {s_("Name:")}
          <input _type="text" name="name" onChange={self.handle(handleChange)} value={self.state.name} />
        </label>
        <input _type="submit" />
      </form>
      <ul>
        {a_(
          Array.mapU(self.state.nameArray, (. name) =>
            <li key={name}>{s_(name)}</li>
          )
        )}
      </ul>
    </div>,
  };
};

動作はシンプルで、input要素に名前を入力して、submitボタンを押すとその名前をリストに追加します。
そして、名前のリストをli要素として出力するというものです。

以下がこのサンプルアプリを作成した際にハマったことになります。

input type

typeはReasonMLでは予約語なので、以下のように使用することができません。

<input type="text" name="name" />

公式のドキュメントに対処方法が記載されています。
https://reasonml.github.io/reason-react/docs/en/invalid-prop-name.html#docsNav
_typeとアンダースコアを前につけることで回避しています。

<input _type="text" name="name" />

余談ですが、valも予約語で、これを変数名に使用すると、以下のコンパイルエラーになります。

Error: 1576: val is a reserved keyword, it cannot be used as an identifier. Try `val_' instead

こちらは、アンダースコアを後ろにつけるので、混乱しそうです。
(アンダースコアを変数名の先頭につけると、使用しない変数という意味になるので、当たり前といえばそうなのですが。。)

event.target.value

JavaScriptでは、inputタグに入力された値をonChangeで受け取って利用することがよくあります。
値の取得方法は、引数で受け取ったeventのevent.target.valueなのですが、ReasonMLの場合は、これでは取得できません。
こちらも公式に利用方法が書いてあります。
https://reasonml.github.io/reason-react/docs/en/event.html#docsNav

公式は、ワンラインになっていますが、私は以下のようにして利用しました。

let handleChange = (e, self) => {
  let target = ReactEventRe.Form.target(e);
  let name = ReactDOMRe.domElementToObj(target)##value;
  self.ReasonReact.send(ChangeText(name));
};

出力結果を見ると、まさにevent.target.valueに変換されていました。

var handleChange = function (e, self) {
  var target = e.target;                                                                                                                                                                                                                       
  var name = target.value;
  return Curry._1(self[/* send */4], /* ChangeText */Block.__(1, [name]));
};

引数のselfについて

reason-reactでは、stateを管理するためにselfというオブジェクトがあります。
通常は、render関数の引数として渡ってくるのですが、

<input _type="text" onChange={self.handle(handleChange)} />

のようにself.handleを利用することで、render関数外に定義された関数でもselfを触ることができます。

event.preventDefault

formのsubmitはデフォルトではページ遷移をしてしまうので、それをキャンセルしたいことがよくあります。
JavaScriptでは、event.preventDefaultを使用するのですが、これまたReasonmlの場合、別の手段になります。

こちらは、公式のドキュメントで見つからずソースコードのインラインドキュメントから対応方法がわかりました。
https://github.com/reasonml/reason-react/blob/380358e5894d4223e7dd9c1fb2df72f0756231bc/src/reactEventRe.rei#L1

let handleSubmit = (e, self) => {
  ReactEventRe.Form.preventDefault(e);
  /* "省略" */
}

今回は、formの挙動をキャンセルしたいので、ReactEventRe.Form.preventDefaultを使用しています。
マウスのイベントをキャンセルしたい場合は、ReactEventRe.Mouse.preventDefaultのように、各イベントごとに違うモジュールを利用するので注意です。

まとめ

ReasonMLは、JavaScriptのシンタックスに近いとはいえ、やはりハマりどころはある印象です。
とくに型まわりで考えることが多いと思っています。

逆に型の変換さえうまく行けば、Reactの知識がそのまま活用できます!
型自体は、すばらしいものなので、世の情報量が増えること、コンパイルエラーがわかりやすくなることが大切かなと思いました。

少しでも、ReasonMLを使う際に役立つ情報となれば幸いです。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away