White Box技術部

WEB開発のあれこれ(と何か)

【React】Quillを表示するカスタムフックと、それをReact Hook Formで使うサンプル

QuillというWYSIWYGエディタをReact Hook Formで使うメモです。

主要ライブラリ

動作時のライブラリバージョンは以下になります。

  • dependencies
    • "react": "18.2.0"
    • "react-hook-form": "7.44.3"
    • "quill": "1.3.7"
    • "react-quilljs": "1.3.3"
  • devDependencies
    • "@types/node": "20.4.10"
    • "@types/quill": "2.0.10"
    • "@types/react": "18.2.20"
    • "@types/react-dom": "18.2.7"

カスタムフック作成

Quillを使い回せるようにカスタムフックにしました。

hooks/useQuill.ts

import 'quill/dist/quill.snow.css';
import { useEffect, useState } from 'react';
import { useQuill } from 'react-quilljs';

export const useReactQuill = (defaultValue?: string) => {
  const { quill, quillRef } = useQuill();
  const [value, setValue] = useState(defaultValue ?? '');

  const imageHandler = () => {
    const input = document.createElement('input');
    input.setAttribute('type', 'file');
    input.setAttribute('accept', 'image/*');
    input.click();

    input.onchange = () => {
      if (!input) {
        return;
      }
      const file: File = (input.files as FileList)[0];

      if (/^image\//.test(file.type)) {
        saveImage(file);
      } else {
        alert('画像のみアップロードできます。');
      }
    };
  };

  const saveImage = async (file: File) => {
    const imageUrl = await uploadImage(file); // TODO: 好みの画像アップロード処理を書いて、画像へのアクセスURLを返してあげる
    if (quill) {
      const range = quill.getSelection();
      if (range) {
        quill.insertEmbed(range.index, 'image', imageUrl);
      }
    }
  };

  useEffect(() => {
    if (quill && value) {
      quill.clipboard.dangerouslyPasteHTML(value);
    }
    if (quill) {
      quill.getModule('toolbar').addHandler('image', imageHandler);
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      quill.on('text-change', (delta, oldDelta, source) => {
        setValue(quill.root.innerHTML);
      });
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [quill]);

  return { quillRef, value, setValue } as const;
};

uploadImageは別途実装が必要です。 画像を埋め込まないのであれば、imageHandler関連部分をまるっと削除しても大丈夫です。

Quill表示の極小サンプル

React Hook Formで上記のカスタムフックを利用する極小サンプルです。

import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { useReactQuill } from '@/hooks/useQuill';

type Form = {
  description: string;
};
type Props = {
  description?: string;
};

export const Sample = ({ description }: Props) => {
  const { quillRef, value } = useReactQuill(description);
  const {
    register,
    setValue,
  } = useForm<Form>({ mode: 'onBlur' });

  useEffect(() => {
    // register('description', { required: true, minLength: 15 });
    register('description');
  }, [register]);

  useEffect(() => {
    setValue('description', value);
  }, [value]);

  return (<div ref={quillRef} />);
};

サンプルでは入力チェックを入れてませんが、コメントアウトしている箇所のようにして、入力チェックの設定を追加することもできます。