外部のUIライブラリ(Draft.js/React Draft Wysiwyg)とReact Hook Formを統合する方法

2791
外部のUIライブラリ(Draft.js/React Draft Wysiwyg)とReact Hook Formを統合する方法

こんにちは、nishiyama です。

今回、Reactを使ったSPA開発プロジェクトでDraft.js(リッチエディタ)を導入しました。ググるといくつかやり方があったのですが、今のところこの形でよかったなと思う内容を書き留めておきたいと思います。

どうやったか

今回のケースでは、React Hook FormのControllerを使う方法にしました。React Hook Form の Controllerは、外部のUIライブラリやカスタムコンポーネントと React Hook Form を統合させやすくするためのものだからです。

React Hook Form embraces uncontrolled components and native inputs, however it's hard to avoid working with external controlled component such as React-Select, AntD and MUI. This wrapper component will make it easier for you to work with them.

React Hook Formは非制御コンポーネントやネイティブ入力を受け入れますが、React-SelectやAntD、MUIなどの外部制御コンポーネントとの連携は避けられません。このラッパーコンポーネントを使うことで、それらを簡単に扱うことができます。

React Hook Form - Controller

最初はなんとか register, unregister を使おうとしたんですが、うまくいきませんでした(全体的には register, unregiser を使用しているため)。悩みポイントとしてはこの部分でした。

具体例

WysiwygEditor(React Draft Wysiwygをラップしたコンポーネント)

ここではDraft.jsのEditorをラップしたコンポーネントです。プロジェクト的に、Draft.jsの採用が決まりきっていなかったので、コンポーネント化しましたが、そういう背景がなければ不要かもしれません。

import { useState } from "react";
import { EditorState } from "draft-js";
import { Editor } from "react-draft-wysiwyg";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import "draft-js/dist/Draft.css";

interface WysiwygEditorProps {
  value: EditorState;
  onChange: (value: EditorState) => void;
  editorStyle: React.CSSProperties;
  toolbar: object;
}
export const WysiwygEditor: React.FC<WysiwygEditorProps> = ({
  value,
  onChange,
  editorStyle,
  toolbar,
}: WysiwygEditorProps) => {
  const [editorState, setEditorState] = useState(
    value === undefined
      ? EditorState.createEmpty()
      : EditorState.createWithContent(value.getCurrentContent())
  );
  const onChangeEditor = (editorState: EditorState) => {
    setEditorState(editorState);
    onChange(editorState);
  };
  return (
    <Editor
      editorState={editorState}
      onEditorStateChange={onChangeEditor}
      readOnly={false}
      editorStyle={editorStyle}
      placeholder="入力してください"
      toolbar={toolbar}
    />
  );
};
export default WysiwygEditor;

useFormを使ったControllerとの統合

formのsubmitでエディタの内容を受け取ります。受け取る型をModelAとして定義します。それから、React Hook Form の useForm を使用して、submitの結果を受け取るための handleSubmit, Controllerを通信するための control、

// 受け取る型をModelAとして定義します
const interface ModelA {
  editorState: EditorState; // 統合するコンポーネントのControllerのnameを変数名にする
}

const {
  handleSubmit,          // submitの結果を受け取るハンドラ
  control,               // React Hook Form にコンポーネントを統合するためのもの
} = useForm<ModelA>({}); // 受け取る型の設定と初期値の設定({})

// jsxでは、onSubmitでisValid, isInValidを指定する
// ex) <form onSubmit={handleSubmit(isValid, isInValiid)}>
const isValid: SubmitHandler<ModelA> = (
  model: ModelA // submitされると、データを受け取れる
) => {
  // ...
}

const isInValid: SubmitHandler<Model> = (errors: any) => {
  // ...
}

Contorllerの使い方

作成した WysiwygEditor を Controller を使って React Hook Form と統合させる部分です。

<form onSubmit={handleSubmit(isValid, isInValiid)}>
  <Controller
    name="editorState"   {/* ModelAの対応する変数名 */}
    control={control}    {/* useFormで定義した control */}
    render={({ field: { onChange } }) => {
    return (
      <WysiwygEditor
        onChange={onChange}
        value={editorState}
        editorStyle={{
          minHeight: "200px",
          maxHeight: "200px",
          outlineStyle: "solid",
          outlineWidth: "2px",
          outlineColor: "#ddd",
        }}
        toolbar={{
          fontFamily: {
            options: [
              "メイリオ",
              "游ゴシック",
              "MS Pゴシック"
            ],
          },
        }}
      />
      );
    }}
  />
</form>

まとめ

外部のUIライブラリである Draft.js(React Draft Wysiwyg) と React Hook Form を統合するために Controller を使う方法を説明しました。 これ以外にも、Material UI(ex. FormControlLabel+Checkbox)を使う場合でも利用できますし、いろんな場面で使うものだと思います。