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} />); };
サンプルでは入力チェックを入れてませんが、コメントアウトしている箇所のようにして、入力チェックの設定を追加することもできます。