Skip to content

TypeError: moduleClass is not a constructor in React-Quill with Quill Mention Module #999

@Kuldeeptak-neelnetworks

Description

@Kuldeeptak-neelnetworks

I'm encountering an error while integrating the quill-mention module with react-quill in a Next.js application. The error message I see is:

Unhandled Runtime Error
TypeError: moduleClass is not a constructor

Call Stack
SnowTheme.addModule
(node_modules/react-quill/node_modules/quill/dist/quill.js (6130:0))
SnowTheme.addModule
(node_modules/react-quill/node_modules/quill/dist/quill.js (6774:0))
eval
(node_modules/react-quill/node_modules/quill/dist/quill.js (6122:0))
Array.forEach

SnowTheme.init

Here is how I’m trying to use the quill-mention module:
"use client";
import React, { Dispatch, SetStateAction, useEffect, useState } from "react";
import dynamic from "next/dynamic";
import Quill from "quill";
import Mention from "quill-mention";
import "react-quill/dist/quill.snow.css";
import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
import TooltipCommon from "@/components/common/TooltipCommon";
import { useCopywriterStore } from "@/Store/CopywriterStore";
import { useUserStore } from "@/Store/UserStore";
import { useEditorStore } from "@/Store/EditorStore";

// Dynamically import ReactQuill to prevent SSR issues
const ReactQuill = dynamic(() => import("react-quill"), { ssr: false });

interface QuillEditorProps {
customerId: string | string[];
updateId: string | string[];
indicatorText: string | string[];
handleEdit: string | string[];
orderId: string | string[];
productFlowId: string | string[];
leadId: string | string[];
technicalId: string | string[];
copywriterId: string | string[];
amendmentId: string | string[];
websiteContentId: string | string[];
setIsOpenReplyModel: Dispatch<SetStateAction>;
setOpenQuill: Dispatch<SetStateAction>;
}

const QuillEditor: React.FC = ({
customerId,
setIsOpenReplyModel,
setOpenQuill,
updateId,
productFlowId,
orderId,
technicalId,
leadId,
indicatorText,
amendmentId,
copywriterId,
websiteContentId,
handleEdit,
handlesave,
editContent,
}: any) => {
const [value, setValue] = useState("");
const [images, setImages] = useState<File[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [fileURLs, setFileURLs] = useState<{ [key: string]: string }>({});
const { fetchCopywriterData }: any = useCopywriterStore();
const { fetchUsersData, userData } = useUserStore();
const {
fetchEditorData,
fetchLeadsEditorData,
fetchOrderEditorData,
fetchTechnicalUpdateData,
fetchAmendmentUpdateData,
fetchProductFlowUpdateData,
fetchWebsiteContentUpdateData,
orderEditorData,
fetchCopywriterUpdateData,
editorData,
addReplyData,
addUpdateData,
loading,
}: any = useEditorStore();

const handleClear = () => {
setValue("");
};

const handleChanges = (value: string, editorData: any) => {
setValue(() =>
editorData.getText().trim() === "" && value === "" ? "" : value
);
};

const handleAddData = async (e: React.FormEvent) => {
e.preventDefault();
if ((value !== "" && value !== "


") || images.length > 0) {
try {
setIsLoading(true);
const formData = new FormData();
formData.append("content", value);
images.forEach((image) => formData.append("files", image));
const requests = [];

    if (indicatorText === "post") {
      requests.push(
        orderId && baseInstance.post(`/updates/order/${orderId}`, formData),
        productFlowId &&
          baseInstance.post(
            `/updates/productflow/${productFlowId}`,
            formData
          ),
        customerId &&
          baseInstance.post(`/updates/customer/${customerId}`, formData),
        leadId && baseInstance.post(`/updates/lead/${leadId}`, formData),
        technicalId &&
          baseInstance.post(
            `/updates/technicaltracker/${technicalId}`,
            formData
          ),
        copywriterId &&
          baseInstance.post(
            `/updates/copywritertracker/${copywriterId}`,
            formData
          ),
        websiteContentId &&
          baseInstance.post(
            `/updates/newwebsitecontent/${websiteContentId}`,
            formData
          ),
        amendmentId &&
          baseInstance.post(`/updates/amendment/${amendmentId}`, formData)
      );
    }

    if (indicatorText === "reply") {
      requests.push(
        baseInstance.post(`/updates/update/reply/${updateId}`, formData)
      );
    }

    const responses = await Promise.all(requests.filter(Boolean));
    responses.forEach((response) => {
      if (response.status === 201) {
        successToastingFunction(response?.data?.message);
        fetchEditorData(customerId);
        fetchOrderEditorData(orderId);
        fetchLeadsEditorData(leadId);
        fetchTechnicalUpdateData(technicalId);
        fetchCopywriterUpdateData(copywriterId);
        fetchAmendmentUpdateData(amendmentId);
        fetchProductFlowUpdateData(productFlowId);
        fetchWebsiteContentUpdateData(websiteContentId);
        setIsOpenReplyModel(false);
        setOpenQuill(false);
        handleClear();
        setImages([]);
        setValue("");
      }
      fetchEditorData(customerId);
    });
  } catch (error) {
    errorToastingFunction(error);
  } finally {
    setIsLoading(false);
  }
} else {
  errorToastingFunction("Please enter text or upload an image to submit.");
}

};

const handleFileUpload = (files: FileList | null) => {
if (files) {
const fileList = Array.from(files);
setImages((prevImages) => [...prevImages, ...fileList]);

  fileList.forEach((file) => {
    if (file.type.startsWith("image/")) {
      const reader = new FileReader();
      reader.onloadend = () => {
        const img = `<img src="${reader.result}" alt="${file.name}" />`;
        setValue((prevValue) => prevValue + img + `</br>`);
      };
      reader.readAsDataURL(file);
    } else {
      const url = URL.createObjectURL(file);
      setFileURLs((prev) => ({ ...prev, [file.name]: url }));
      const link = `<a href="${url}" target="_blank" rel="noopener noreferrer">${file.name}</a>`;
      setValue((prevValue) => prevValue + link + `</br>`);
    }
  });
}

};

const imageHandler = () => {
const inputImage = document.createElement("input");
inputImage.setAttribute("type", "file");
inputImage.setAttribute(
"accept",
"image/, video/, .pdf, .xlsx, .doc, .docx"
);
inputImage.setAttribute("multiple", "true");
inputImage.click();

inputImage.onchange = (e) => {
  handleFileUpload(inputImage.files);
};

};

useEffect(() => {
return () => {
Object.values(fileURLs).forEach((url) => URL.revokeObjectURL(url));
};
}, [fileURLs]);

// Commented out because it causes an error
// useEffect(() => {
// if (Quill) {
// Quill.register("modules/imageResize", ImageResize);
// Quill.register("modules/mention", Mention);
// }
// }, [Quill]);

const toolbarOptions = [
["bold", "italic", "underline", "strike"],
[{ size: ["small", false, "large", "huge"] }],
[{ list: "ordered" }, { list: "bullet" }, { list: "check" }],
[{ script: "sub" }, { script: "super" }],
[{ indent: "-1" }, { indent: "+1" }],
[{ direction: "rtl" }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
[{ align: [] }],
];

const options = {
debug: "info",
modules: {
toolbar: toolbarOptions,
imageResize: {
parchment: Quill.import("parchment"),
modules: ["Resize", "DisplaySize"],
},
mention: {
allowedChars: /^[A-Za-z\s]*$/,
mentionDenotationChars: ["@"],
source: async (
searchTerm: string,
renderItem: (data: any[]) => void
) => {
try {
await fetchUsersData(); // Ensure data is fetched first
const filteredUsers = userData
.filter((user: any) =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
)
.map((user: any) => ({ value: user.name }));
renderItem(filteredUsers);
} catch (error) {
console.error("Error fetching mention users:", error);
renderItem([]);
}
},
},
},
placeholder: "Compose an epic...",
theme: "snow",
};

return (
<>



<ReactQuill
placeholder={options.placeholder}
theme={options.theme}
modules={options.modules}
value={value}
onChange={(value, _, __, editor) => {
handleChanges(value, editor);
}}
/>



{isLoading ? (

) : indicatorText === "reply" ? (
"Reply"
) : (
"Update"
)}

      <div onClick={imageHandler} className="w-fit cursor-pointer ">
        <TooltipCommon text="Add Files">
          <div className="hover:bg-gray-100 px-2 py-1 rounded">
            <AddFilesDarkUIconSVG />
          </div>
        </TooltipCommon>
      </div>
    </div>
  </form>
</>

);
};

export default QuillEditor;

Question:

I've integrated the quill-mention module into my react-quill component but am running into an issue. The error TypeError: moduleClass is not a constructor appears, and the call stack indicates a problem with SnowTheme.addModule in the Quill.js file.

I've tried to register the module as follows:
useEffect(() => { if (Quill) { Quill.register("modules/imageResize", ImageResize); Quill.register("modules/mention", Mention); } }, [Quill]);

However, this causes the mentioned error. I've also commented out the registration as it seems to be the cause of the issue.

What I've Tried:

Ensuring that the quill-mention module is correctly imported and used.
Checking the version compatibility of react-quill, quill, and quill-mention.
Referencing the Quill documentation for proper module registration.
What I Need Help With:

Why am I encountering this TypeError?
How can I properly integrate the quill-mention module with react-quill?
Are there any additional steps or configurations required to avoid this error?
Any guidance or suggestions would be greatly appreciated!

This is the Error:-
image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions