強欲で謙虚なツボツボ

趣味の読書の書の方

SSRでreact-quillを使うときの対応(Next.js)

 

とりあえず使えればいい

何も考えずにNext.jsでreact-quillを使うとこうなる。

$ npm install --save-dev react-quill
import { useState } from 'react'
import ReactQuill from 'react-quill'
import 'react-quill/dist/quill.snow.css'

export default function Quill() {
  const [value, setValue] = useState<string>()

  return (
    <ReactQuill
      theme="snow"
      value={value}
      onChange={(v) => setValue(v)}
    />
  )
}

これだと次のようなエラーが出る。

ReferenceError: document is not defined

対処法はこちらを参照(1)。

importしたreact-quillの処理が働いているのだが、SSRモードでwindow.documentが生成される前なので、エラーとなってしまう。

これはnext/dynamicを利用すると解決する。
next/dynamicを使うことで、importするのを画面のレンダリングまで待つことができる。

修正後はこうなる。

import dynamic from 'next/dynamic'
import { useState } from 'react'
import 'react-quill/dist/quill.snow.css'

const ReactQuill = dynamic(() => import('react-quill'), { ssr: false })

export default function Quill() {
  const [value, setValue] = useState<string>()

  return (
    <ReactQuill
      theme="snow"
      value={value}
      onChange={(v) => setValue(v)}
    />
  )
}

 

react-quillを使うだけであれば、ここまでで十分。

 

ReactQuillインスタンスへのアクセス

ここからは、文字数カウントなどをするためにReactQuillインスタンスへのアクセスを行うための対応。

react-quillのメソッドなどは公式を参照(2)。

import dynamic from 'next/dynamic'
import { useMemo, useRef, useState } from 'react'
import 'react-quill/dist/quill.snow.css'

const ReactQuill = dynamic(() => import('react-quill'), { ssr: false })

export default function Quill() {
  const [value, setValue] = useState<string>()
  const quillRef = useRef<any>() // 本当はuseRef<ReactQuill>にしたいけど、dynamic importしているのでanyにするしかなさそう

  // 文字数をカウントしたい
  const strCount = useMemo(() => {
    if (!quillRef.current) return

    const editor = quillRef.current.getEditor()
    const unprivilegedEditor = quillRef.current.makeUnprivilegedEditor(editor)
    return unprivilegedEditor.getLength() - 1
  }, [value])

  return (
    <>
      <ReactQuill
        ref={ref}
        theme="snow"
        value={value}
        onChange={(v) => setValue(v)}
      />
      <p>文字数:{strCount}</p>
    </>
  )
}

これだと次のようなエラーが出るので、続けて対応していく。

Property 'ref' does not exist on type 'IntrinsicAttributes & ReactQuillProps'

これについては、こちらが一番詳しかったので参照。(3)

dynamic importするときに、refを設定できるようにReactQuillPropsを継承してinterfaceを作成してあげるとできた。

import dynamic from 'next/dynamic'
import { useMemo, useRef, useState } from 'react'
import { ReactQuillProps } from 'react-quill'
import 'react-quill/dist/quill.snow.css'

const ReactQuill = dynamic(
  async () => {
    const { default: RQ } = await import('react-quill')
    interface Props extends ReactQuillProps {
      forwardedRef: any
    }
    return (props: Props) => <RQ ref={props.forwardedRef} {...props} />
  },
  { ssr: false }
)

export default function Quill() {
  const [value, setValue] = useState<string>()
  const quillRef = useRef<any>() // 本当はuseRef<ReactQuill>にしたいけど、dynamic importしているのでanyにするしかなさそう

  // 文字数をカウントしたい
  const strCount = useMemo(() => {
    if (!quillRef.current) return

    const editor = quillRef.current.getEditor()
    const unprivilegedEditor = quillRef.current.makeUnprivilegedEditor(editor)
    return unprivilegedEditor.getLength() - 1
  }, [value])

  return (
    <>
      <ReactQuill
        forwardedRef={quillRef}
        theme="snow"
        value={value}
        onChange={(v) => setValue(v)}
      />
      <p>文字数:{strCount}</p>
    </>
  )
}

 

gitの差分はこちら

https://github.com/di-kotobuki/react-quill/commit/e417f36449b57ae7867fd8a8f634a64e43ff4455

参照

  1. document is not defined · Issue #292 · zenoamaro/react-quill · GitHub
  2. react-quill - npm
  3. node.js - How to access ReactQuill Ref when using dynamic import in NextJS? - Stack Overflow