Quill: Is there a guide for how to use with reactjs?

Created on 8 May 2017  路  5Comments  路  Source: quilljs/quill

I just want a simple WYSIWYG editor with a minimal set of features.

I'd prefer not to use a third party reactjs integration project as it seems these projects are often not fully up to date and maintained.

Is there some instructions somewhere for a simple way to run Quill in ReactJS?

thanks

Most helpful comment

@allenwipf I have a component that creates quill directly, and it's working good for me. Above mentioned zenoamaro/react-quill integration seems to have issue with re-creating a quill instance too often, affecting performance and sometimes toolbar disappears while you work with it.

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Quill from 'quill';

import { variableType } from 'enums/variablesTypes';
import { getBrowserStatus } from 'reducers/browser';
import { BROWSER_TEXT_READY } from 'actions/browser';

import 'quill/dist/quill.bubble.css';
import './TextItem.scss';

import { html } from 'components/HtmlComponent';
const bem = html.bem('TextItem');
const gridBem = html.bem('EditorGrid');

var Size = Quill.import('attributors/style/size');
Size.whitelist = [
    '1em', '1.25em', '1.5em', '2em', '3em', '4em'
];
Quill.register(Size, true);

var FontAttributor = Quill.import('formats/font');
FontAttributor.whitelist = [
    'open-sans', 'roboto', 'proza-libre'
];
Quill.register(FontAttributor, true);

Quill.debug(false);


@connect(
    state => ({
        textReady: getBrowserStatus(state).code === BROWSER_TEXT_READY,
    })
)
export default class TextItem extends React.PureComponent {

    static propTypes = {
        isEditable: PropTypes.bool,
        selected: PropTypes.bool,
        config: PropTypes.object,
        textUpdateHandler: PropTypes.func,
        variables: PropTypes.arrayOf(variableType),
    };

    static defaultProps = {
        variables: [],
    };

    static textItemProperties = [
        'text',
        'position'
    ];

    static getText(props) {
        return props.config.text
    }

    onChangeTimeout = null;
    editorRef = React.createRef();
    editor = null;
    html = null;
    userUpdate = false;

    options = {
        readOnly: !this.props.selected,
        bounds: '.' + gridBem.block(),
        theme: 'bubble',
        modules: {
            toolbar: [
                [
                    { 'header': '1'}, {'header': '2'}, { 'font': ['','open-sans', 'roboto', 'proza-libre'] },
                    {'size': ['1em', '1.25em', '1.5em', '2em', '3em', '4em']}
                ],
                ['bold', 'italic', 'underline'],
                [{ 'color': [] }, { 'background': [] }],
                [{ 'align': ['','center','right'] }],
                [{ 'list': 'ordered'}, {'list': 'bullet'}],
            ]
        },
        formats: 
            ['align', 'background', 'header', 'font', 'size', 'align', 'bold', 'italic', 'underline',
                'strike', 'script', 'list', 'bullet', 'indent', 'link', 'color', 'clean'],
    }

    componentDidMount() {
        this.editor = new Quill(this.editorRef.current, this.options);
        this.editor.root.innerHTML = TextItem.getText(this.props);
        this.editor.update();
        this.editor.on('text-change', this.onChange);
    }
    componentDidUpdate() {
        if( this.props.textReady ){
            this.editor.root.innerHTML = TextItem.getText(this.props);
            this.editor.update();
        }
    }


    onChange = (value, delta, source) => {
        /* source === api | user */
        if(source === 'api'){
            return;
        }

        this.html = this.editor.root.innerHTML;

        if (this.onChangeTimeout) {
            clearTimeout(this.onChangeTimeout);
            this.onChangeTimeout = null;
        }

        this.onChangeTimeout = setTimeout(() => this.commitValue(), 300);
    }

    commitValue() {
        clearTimeout(this.onChangeTimeout);
        this.onChangeTimeout = null;

        if (this.props.isEditable) {
            this.userUpdate = true;
            this.props.textUpdateHandler(this.html);
        }
    }

    render() {
        const isReadOnly = !this.props.isEditable || (this.props.isEditable && !this.props.selected);

        return (
            <div className={bem.block()}>
                <div className={bem.element('positioner', {
                    position: this.props.config.position,
                })}
                >
                    <div ref={this.editorRef}
                        className={bem.element('editor', {
                            readonly: isReadOnly,
                        })}
                    >
                    </div>
                </div>
            </div>
        );
    }

}

All 5 comments

Hi @lunikernel,

I don't know of such guide. Have you seen zenoamaro/react-quill? Would that be helpful to check an approach to use Quill with React?

Cheers.

Just in case, you can create an instance of a Quill() in componentDidMount() and also immediately subscribe to its on('text-change') events. Filter out source==='api' and record 'user' changes to your DB periodically (I do each 250ms):

 componentDidMount() {
        this.editor = new Quill(this.editorRef.current, this.options);
        this.editor.setText('');
        this.editor.clipboard.dangerouslyPasteHTML(0,  TextItem.getText(this.props));
        this.editor.on('text-change', this.onChange);
    }
    componentDidUpdate(prevProps) {
        if(TextItem.getText(prevProps) != TextItem.getText(this.props)){
            this.editor.setText('');
            this.editor.clipboard.dangerouslyPasteHTML(0,  TextItem.getText(this.props));
        }
    }


    onChange = (value, delta, source) => {

        /* Sort out API updates to avoid loop component updates */
        if(source === 'api'){
            return;
        }
        this.html = this.editor.root.innerHTML;


        if (this.onChangeTimeout) {
            clearTimeout(this.onChangeTimeout);
            this.onChangeTimeout = null;
        }

        this.onChangeTimeout = setTimeout(() => this.commitValue(), 250);
    }

    commitValue() {
        clearTimeout(this.onChangeTimeout);
        this.onChangeTimeout = null;

        if (this.props.isEditable) {
            this.props.textUpdateHandler(this.html);
        }
    }

    render() {
        const isReadOnly = !this.props.isEditable || (this.props.isEditable && !this.props.selected);

        return (
            <div className={bem.block()}>
                <div className={bem.element('positioner', {
                    position: this.props.config.position,
                })}
                >
                    <div ref={this.editorRef}
                        className={bem.element('editor', {
                            readonly: isReadOnly,
                        })}
                    >
                    </div>
                </div>
            </div>
        );
    }

@ilya-spy do you happen to have a working example of the above code you could share?

@allenwipf I have a component that creates quill directly, and it's working good for me. Above mentioned zenoamaro/react-quill integration seems to have issue with re-creating a quill instance too often, affecting performance and sometimes toolbar disappears while you work with it.

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import Quill from 'quill';

import { variableType } from 'enums/variablesTypes';
import { getBrowserStatus } from 'reducers/browser';
import { BROWSER_TEXT_READY } from 'actions/browser';

import 'quill/dist/quill.bubble.css';
import './TextItem.scss';

import { html } from 'components/HtmlComponent';
const bem = html.bem('TextItem');
const gridBem = html.bem('EditorGrid');

var Size = Quill.import('attributors/style/size');
Size.whitelist = [
    '1em', '1.25em', '1.5em', '2em', '3em', '4em'
];
Quill.register(Size, true);

var FontAttributor = Quill.import('formats/font');
FontAttributor.whitelist = [
    'open-sans', 'roboto', 'proza-libre'
];
Quill.register(FontAttributor, true);

Quill.debug(false);


@connect(
    state => ({
        textReady: getBrowserStatus(state).code === BROWSER_TEXT_READY,
    })
)
export default class TextItem extends React.PureComponent {

    static propTypes = {
        isEditable: PropTypes.bool,
        selected: PropTypes.bool,
        config: PropTypes.object,
        textUpdateHandler: PropTypes.func,
        variables: PropTypes.arrayOf(variableType),
    };

    static defaultProps = {
        variables: [],
    };

    static textItemProperties = [
        'text',
        'position'
    ];

    static getText(props) {
        return props.config.text
    }

    onChangeTimeout = null;
    editorRef = React.createRef();
    editor = null;
    html = null;
    userUpdate = false;

    options = {
        readOnly: !this.props.selected,
        bounds: '.' + gridBem.block(),
        theme: 'bubble',
        modules: {
            toolbar: [
                [
                    { 'header': '1'}, {'header': '2'}, { 'font': ['','open-sans', 'roboto', 'proza-libre'] },
                    {'size': ['1em', '1.25em', '1.5em', '2em', '3em', '4em']}
                ],
                ['bold', 'italic', 'underline'],
                [{ 'color': [] }, { 'background': [] }],
                [{ 'align': ['','center','right'] }],
                [{ 'list': 'ordered'}, {'list': 'bullet'}],
            ]
        },
        formats: 
            ['align', 'background', 'header', 'font', 'size', 'align', 'bold', 'italic', 'underline',
                'strike', 'script', 'list', 'bullet', 'indent', 'link', 'color', 'clean'],
    }

    componentDidMount() {
        this.editor = new Quill(this.editorRef.current, this.options);
        this.editor.root.innerHTML = TextItem.getText(this.props);
        this.editor.update();
        this.editor.on('text-change', this.onChange);
    }
    componentDidUpdate() {
        if( this.props.textReady ){
            this.editor.root.innerHTML = TextItem.getText(this.props);
            this.editor.update();
        }
    }


    onChange = (value, delta, source) => {
        /* source === api | user */
        if(source === 'api'){
            return;
        }

        this.html = this.editor.root.innerHTML;

        if (this.onChangeTimeout) {
            clearTimeout(this.onChangeTimeout);
            this.onChangeTimeout = null;
        }

        this.onChangeTimeout = setTimeout(() => this.commitValue(), 300);
    }

    commitValue() {
        clearTimeout(this.onChangeTimeout);
        this.onChangeTimeout = null;

        if (this.props.isEditable) {
            this.userUpdate = true;
            this.props.textUpdateHandler(this.html);
        }
    }

    render() {
        const isReadOnly = !this.props.isEditable || (this.props.isEditable && !this.props.selected);

        return (
            <div className={bem.block()}>
                <div className={bem.element('positioner', {
                    position: this.props.config.position,
                })}
                >
                    <div ref={this.editorRef}
                        className={bem.element('editor', {
                            readonly: isReadOnly,
                        })}
                    >
                    </div>
                </div>
            </div>
        );
    }

}

Thank you ilya-spy but your code is a little hard to understand because it imports other custom made components. Do anyone has a code on github that use redux or not, at least react ?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sferoze picture sferoze  路  3Comments

markstewie picture markstewie  路  3Comments

benbro picture benbro  路  3Comments

DaniilVeriga picture DaniilVeriga  路  3Comments

lastmjs picture lastmjs  路  3Comments