react-quill: Server side rendering broken since Sep 20th commits

/node_modules/quill/dist/quill.js:2245
var elem = document.createElement('div');
ReferenceError: document is not defined

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 12
  • Comments: 22 (1 by maintainers)

Most upvoted comments

This is the cleanest method I have found to import modules that use window or document:

const ReactQuill = typeof window === 'object' ? require('react-quill') : () => false;

// Use ReactQuill as desired

<ReactQuill
  value={value}
  onChange={handleChange}
/>

I was able to work around this limitation by instantiating react-quill only on the client. Note that import only works on the top level, so I use require.

I imagine the maintainers of this module should be able to prevent calls to the DOM when it’s not available, without too much trouble. Big +1 for that change.

Here’s my wrapper component to support isomorphic rendering:

import React, {Component} from 'react'

export default class FormHtmlEditor extends Component {
  constructor(props) {
    super(props)
    if (document) {
      this.quill = require('react-quill')
    }
  }

  render() {
    const Quill = this.quill
    if (Quill) {
      return (
        <Quill
          onChange={this.props.onChange}
          theme="bubble"
          value={this.props.value}
        />
      )
    } else {
      return null
    }
  }
}

I wrote this with dynamic module

import { Spin } from 'antd';
import dynamic from 'next/dynamic';

const QuillNoSSRWrapper = dynamic(
  import('react-quill')
  {
    ssr: false,
    loading: () => <Spin />,
  }
);

export default QuillNoSSRWrapper;

this is how to check if it run in a browser or not (i changed a code from @calvintennant ):

import React, { Component } from 'react'

export default class FormHtmlEditor extends Component {
  constructor(props) {
    super(props)
    if (typeof window !== 'undefined') {
      this.ReactQuill = require('react-quill')
    }
  }

  render() {
    const ReactQuill = this.ReactQuill
    if (typeof window !== 'undefined' && ReactQuill) {
      return (
        <ReactQuill
          onChange={this.props.onChange}
          theme="bubble"
          value={this.props.value}
        />
      )
    } else {
      return <textarea />;
    }
  }
}

This is what i did works fine with SSR on next js

import { SPLayout } from '@layout';
import dynamic from 'next/dynamic';
import { FC, ReactElement } from 'react';

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

const Create: FC = (): ReactElement => {
	return (
		<SPLayout>
			<ReactQuill
				value={'test'}
				onChange={e => {
					console.log(e);
				}}
			/>
		</SPLayout>
	);
};

export default Create;

This is the cleanest method I have found to import modules that use window or document:

const ReactQuill = typeof window === 'object' ? require('react-quill') : () => false;

// Use ReactQuill as desired

<ReactQuill
  value={value}
  onChange={handleChange}
/>

This is the best solution! @samlogan Thanks!!

@Kamez you can try this solution. This worked for my application.

In my eventform I had a field like this:

<Field
            name='body'
            component={renderQuill}
            placeholder='Tell your story'
          />

The component renderQuill has the following setup:

renderQuill.js

import React from 'react'
import dynamic from 'next/dynamic'


const QuillNoSSRWrapper = dynamic(
  () => import('./renderQuillReact'),
  { ssr: false, loading: () => <p>Loading ...</p> },
)

class renderQuill extends React.Component {
  render () {
    const { input, label, type } = this.props
    return (
        <QuillNoSSRWrapper input={input} label={label} type={type}/>
    )
  }
}


export default renderQuill

As you notice I import a second component ‘./renderQuillReact’ that contains the resize module.

renderQuillReact.js component

import React from "react";
import ReactQuill, { Quill } from "react-quill";
import ImageResize from "quill-image-resize-module-react"; // import as default
import { ImageDrop } from "quill-image-drop-module";
Quill.register("modules/imageResize", ImageResize);
Quill.register("modules/imageDrop", ImageDrop);

class renderQuillReact extends React.Component {
    render () {
  
      const { input, label, type } = this.props
      return (
        <ReactQuill
        modules={Event.modules}
        label={label}
        formats={Event.formats}
        {...input}
        type={type}
        onChange={(newValue, delta, source) => {
          if (source === "user") {
            input.onChange(newValue);
          }
        }}
        onBlur={(range, source, quill) => {
          input.onBlur(quill.getHTML());
        }}
      />
      )
    }
  }


Event.modules = {
  toolbar: [
    [{ header: "1" }, { header: "2" }, { font: [] }],
    ["bold", "italic", "underline", "blockquote"],
    [{ list: "ordered" }, { list: "bullet" }],
    ["link", "image", "video"],
    ["clean"],
    ["code-block"]
  ],
  imageDrop: true,
  imageResize: {
    handleStyles: {
      backgroundColor: "black",
      border: "none",
      color: "white"
    },
    modules: ["Resize", "DisplaySize", "Toolbar"]
  }
};

Event.formats = [
  "header",
  "font",
  "size",
  "bold",
  "italic",
  "underline",
  "blockquote",
  "list",
  "bullet",
  "link",
  "image",
  "video",
  "code-block"
];

export default renderQuillReact;

Hope this solution works for you!

Following solution works like a charm:

    const QuillNoSSRWrapper = dynamic(import('react-quill'), {
        ssr: false,
        loading: () => <p>Loading ...</p>,
    })
    return(
QuillNoSSRWrapper theme="snow"/>)

This is the cleanest method I have found to import modules that use window or document:

const ReactQuill = typeof window === 'object' ? require('react-quill') : () => false;

// Use ReactQuill as desired

<ReactQuill
  value={value}
  onChange={handleChange}
/>

You are right. But It is not working like textarea. How can ı pass form data via this editor.

I wrote this with dynamic module

import { Spin } from 'antd';
import dynamic from 'next/dynamic';

const QuillNoSSRWrapper = dynamic(
  import('react-quill')
  {
    ssr: false,
    loading: () => <Spin />,
  }
);

export default QuillNoSSRWrapper;

this solution makes the situation what is re-rendering when onChange event performed

Same (nuxt3, SSR)… using ES modules, cant use require, and dynamic import() seems to be behaving strangely with a condition…

Just curious, why cant quill just do nothing if its not running in the client?

All of my components are bundled up with WebPack and sent from the server using renderToString. When quill is included in the server side bundle via import my server crashes with the folowing output:

/path/to/src/node_modules/quill/dist/quill.js:2377
  var elem = document.createElement('div');
             ^

ReferenceError: document is not defined
    at Object.<anonymous> (/path/to/src/node_modules/quill/dist/quill.js:2377:13)
    at __webpack_require__ (/path/to/src/node_modules/quill/dist/quill.js:36:30)
    at Object.<anonymous> (/path/to/src/node_modules/quill/dist/quill.js:1669:2)
    at __webpack_require__ (/path/to/src/node_modules/quill/dist/quill.js:36:30)
    at Object.<anonymous> (/path/to/src/node_modules/quill/dist/quill.js:76:15)
    at __webpack_require__ (/path/to/src/node_modules/quill/dist/quill.js:36:30)
    at Object.defineProperty.value (/path/to/src/node_modules/quill/dist/quill.js:7798:14)
    at __webpack_require__ (/path/to/src/node_modules/quill/dist/quill.js:36:30)
    at Object.<anonymous> (/path/to/src/node_modules/quill/dist/quill.js:63:19)
    at __webpack_require__ (/path/to/src/node_modules/quill/dist/quill.js:36:30)

My hack as above using require was to work around this crash.