zenoamaro / react-quill

A Quill component for React.
https://zenoamaro.github.io/react-quill
MIT License
6.78k stars 923 forks source link

Server Side Rendering (NextJS) and React Quill (my descent to madness) #962

Closed BOBONA closed 7 months ago

BOBONA commented 7 months ago

I've seen many issues discussing the problems that arise when using react-quill with NextJS, however none of them seem to address more involved situations.

  1. The most basic issue, "document is not defined," can be solved in NextJS with dynamic imports.

    const ReactQuillDynamic = dynamic(() => import("./quill-dynamic"), {
    ssr: false,
    loading: () => (
    <textarea
      className="resize-none border border-black p-1 italic text-gray-500"
      value="Loading..."
      readOnly
    />
    ),
    });
  2. Notice that I import from my own file "quill-dynamic" instead of "react-quill." This appears to be necessary to use forwardRefs.

    
    import ReactQuill from "react-quill"

// Incredibly hacky but necessary to allow for forward refs afaik export default function QuillForwardRefWrapper({ forwardRef, ...props }: React.ComponentProps & { forwardRef: React.RefObject }) { return <ReactQuill ref={forwardRef} {...props} /> }


3. Quill's deltas unlock a lot of power. I'm using them to translate rich text into a PDF. However, they're very useful even for simple operations, like trying to make a single line input. Now, if I try to just input Deltas normally, I get "TypeError: quill__WEBPACK_IMPORTED_MODULE_4__.Delta is not a constructor." I wish I knew enough about Webpack to understand this, but alas, I am stuck going with some Stackoverflow solutions:
```JSX
import { Quill } from "react-quill";
import { type Delta as DeltaType } from "quill";
const Delta = Quill.import("delta") as typeof DeltaType;
type Delta = DeltaType;
  1. Except, now we're back at the document is not found issue! I thought I could try to ignore this, but the issue seems to be completely breaking the NextJS server.

I would really appreciate some help figuring this out. I've seen issues mentioning Deltas and SSR separately but none together. I've tried a lot of random things but cannot get this working.

Here's my full simplified example:

"use client";

import dynamic from "next/dynamic";
import React, { useRef, useState } from "react";
import type ReactQuill from "react-quill";

import "react-quill/dist/quill.snow.css";

import { Quill } from "react-quill";
import { type Delta as DeltaType } from "quill";
const Delta = Quill.import("delta") as typeof DeltaType;
type Delta = DeltaType;

const ReactQuillDynamic = dynamic(() => import("./quill-dynamic"), {
  ssr: false,
  loading: () => (
    <textarea
      className="resize-none border border-black p-1 italic text-gray-500"
      value="Loading..."
      readOnly
    />
  ),
});

const toolbarOptions = [
  ["bold", "italic", "underline", "strike"], 
];

const editorModules = {
  toolbar: toolbarOptions,
};

const Editor = function Editor(props: {
  className?: string;
  placeholder?: string;
  singleLine?: boolean;
}) {
  const ref = useRef<ReactQuill>(null);
  // const [value, setValue] = useState("");
  const [value, setValue] = useState<Delta>(new Delta());

  const processValue = (value: Delta) => {
    if (props.singleLine) {
      let firstLine = new Delta();
      value.eachLine((line, attributes, idx) => {
        if (idx === 0) {
          line.ops?.forEach((op) => {
            firstLine = firstLine.insert(op.insert, { ...op.attributes });
          });
          firstLine = firstLine.insert("\n", { ...attributes });
        }
      });
      return firstLine;
    } else {
      return value;
    }
  };

  return (
    <ReactQuillDynamic
      theme="snow"
      forwardRef={ref}
      modules={editorModules}
      value={value}
      onChange={(value, delta, source, editor) => { setValue(processValue(editor.getContents())); }}
      // onChange={(value) => setValue(value)}
      onBlur={() => ref.current?.focus()}
      className={props.className}
      placeholder={props.placeholder}
    />
  );
};

export default Editor;
BOBONA commented 7 months ago

I figured out a solution, although it is extremely hacky.

The issue here is that I find myself needing to construct new Deltas throughout my code. While Quill.import enables this, it seems to be at the cost of SSR (specifically the import { Quill }).

The solution is to simply not construct new Deltas. If you want a new Delta, use a function like this

export const makeEmptyDelta = (editor: ReactQuill | null) => {
  return editor?.getEditor().clipboard.convert("");
};

Now you can get away with using Deltas fully and only importing the type.