I'm using package.json
"next": "13.0.6",
"react": "18.2.0",
"react-dom": "18.2.0",
Case 1
const [domLoaded, setDomLoaded] = useState(false);
useEffect(() => {
setDomLoaded(true);
}, []);
...
{domLoaded && (
<>
// ...something need to be rendered after DOM loaded like below
<Toolbar />
<ReactQuill
ref={quillRef}
value={value}
modules={modules}
onChange={setValue}
theme="snow"
/>
</>
)}
Case2
{typeof window !== "undefined" && (
<>
<Toolbar />
<ReactQuill
ref={quillRef}
style={{ height: "60vh" }}
value={value}
modules={modules}
onChange={setValue}
theme="snow"
/>
</>
)}
I think Case 1 is same as Case 2 but, Case1 is ok, but Case2 throw an Error says that "Error: Hydration failed because the initial UI does not match what was rendered on the server."
What is difference between two cases ? Thanks.
Entire code
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import "react-quill/dist/quill.snow.css";
const ReactQuill = typeof window === "object" ? require("react-quill") : () => false;
const Quill = typeof window === "object" ? require("react-quill").Quill : () => false;
import Toolbar from "~/@components/molecule/Editor/Toolbar";
import useInput from "~/@hooks/useInput";
import { ContentService } from "~/@services/content";
export default function QuillTest() {
const quillRef = useRef();
const [value, setValue] = useState();
const [domLoaded, setDomLoaded] = useState(false);
useEffect(() => {
setDomLoaded(true);
}, []);
const imageHandler = useCallback(() => {
const formData = new FormData();
const input = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("accept", "image/*");
input.setAttribute("name", "image");
input.click();
input.onchange = async () => {
if (input.files && input.files[0]) {
console.log("input.files[0]", input.files[0]);
formData.append("image", input.files[0]);
}
const url = await ContentService.imageUpload(formData);
//@ts-ignore
const quill = quillRef.current.getEditor();
const range = quill.getSelection()?.index;
if (typeof range !== "number") return;
quill.setSelection(range, 1);
quill.clipboard.dangerouslyPasteHTML(range, `<img src=${url} alt="image" />`);
};
}, [quillRef]);
const devideHandler = useCallback(() => {
let BlockEmbed = Quill.import("blots/block/embed");
class DividerBlot extends BlockEmbed {}
DividerBlot.blotName = "divider2";
DividerBlot.tagName = "hr";
Quill.register("formats/divider", DividerBlot);
//@ts-ignore
const quill = quillRef.current?.getEditor();
let range = quill.getSelection(true);
quill.insertText(range.index, "\n", Quill.sources.USER);
quill.insertEmbed(range.index + 1, "divider2", true, Quill.sources.USER);
quill.setSelection(range.index + 2, Quill.sources.SILENT);
}, [quillRef]);
const modules = useMemo(() => {
return {
toolbar: {
container: "#toolbar",
handlers: {
image: imageHandler,
divider2: devideHandler,
},
},
};
}, [imageHandler]);
return (
<div>
{typeof window !== "undefined" && (
<>
<Toolbar />
<ReactQuill
ref={quillRef}
style={{ height: "60vh" }}
value={value}
modules={modules}
onChange={setValue}
theme="snow"
/>
</>
)}
</div>
);
}