logo
Basic Utils
Home

React Quill with Quilljs and Example Codes

Table of Contents

  1. Introduction To React Quill and Quilljs
  2. Quill API Usage in React Quill
  3. React Quill with Clipboard Module
  4. React Quill with History Module
  5. Designing in delta format in React Quill
  6. Introduction to parchment
  7. Conclusion

Introduction To React Quill and Quilljs

In an information-driven world, creating a seamless text editing experience is crucial. React Quill is an npm package that relies on Quill.js as its engine. To understand React Quill, we will first explore why you would consider using Quill.js.

Based on Quill Delta Format: Predictable and Reliable

Delta is Quill’s representation of editor content. It is similar to JSON, making it familiar to most developers. It is predictable and reliable, and Quill.js offers an extensive API for manipulating it. The use of the Delta format simplifies data handling, making it easy to process, store, and retrieve editor content.

Easy to set up and get started

Quill.js boasts an incredibly easy setup, making it beginner-friendly. React Quill inherits this simplicity, allowing you to set it up in just a few lines of code. Here’s the simplified setup:

import React, { useState } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css'; // Import Quill's CSS
const RichTextEditor: React.FC = () => {
  const [content, setContent] = useState('');
  return (
    <ReactQuill value={content} onChange={setContent} />
  );
};
export default RichTextEditor;

This example shows how easy it is to set up React Quill.

Programmable Through Quilljs API

Quill.js defines an extensive API to handle text editing rather than relying on the DOM API. Other editors depend on the DOM, which creates a mismatch between what the editor represents (text) and what it operates on (nodes). This complicates even simple tasks, such as determining the text at a particular range.

Here is Quill's API in action:

const format = quill.getFormat(5, 1); // Get format at index 5
console.log(format.bold); // true if bold

By integrating React Quill, you can tap into Quill.js's power of flexibility. Let's dive into more technical details.

Quill API Usage in React Quill

Introduction To Quill API

React Quill relies on Quill.js as its engine. This gives React Quill full access to the Quill's API. Quill's API abstracts complex operations into intuitive functions.

Accessing the quills instance from React quill

To work with Quill from React Quill, you need to fetch its reference. React Quill exposes this reference via the ref prop. Here’s an example of how to obtain it:

import React, { useRef } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
const EditorWithAPI: React.FC = () => {
  const quillRef = useRef<ReactQuill>(null);
  return (
    <div>
      <ReactQuill ref={quillRef} />
    </div>
  );
};
export default EditorWithAPI;

Handling events in React Quill

In react-quill, we handle events using 3 common events:

  1. Text-Change
  2. Selection-change
  3. Editor-change

We make use of the quill.on() method for this in the React Quill API. Here is its syntax:

quill.on('event',callback)

Text-Change Event in React Quill

We use the onChange listener to get notified of any operation in the editor. Here is an example:

quill.on('text-change', (delta, oldDelta, source) => {
        console.log('Content changed:', delta);
      });

The text-change event handler receives the old delta and the new delta. We use this to determine the editor's state before and after the change.

Selection-Change Event in React Quill

Selection-change event listens to changes in the editor's selection.These include cursor placement or text highlight. Here is an example code that applies a background color to all selected text in React Quill:

import React, { useRef, useEffect } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
const HighlightSelectionExample: React.FC = () => {
  const quillRef = useRef<ReactQuill>(null);
  useEffect(() => {
    const quill = quillRef.current?.getEditor();
    if (quill) {
      quill.on('selection-change', (range) => {
        if (range && range.length > 0) {
          // Apply yellow color to the selected text
          quill.formatText(range.index, range.length, { background: 'yellow' });
        }
      });
    }
    // Cleanup listener on component unmount
    return () => {
      quill?.off('selection-change');
    };
  }, []);
  return (
    <div>
      <ReactQuill ref={quillRef} />
      <p style={{ marginTop: '1rem' }}>
        Select some text in the editor to highlight it with a yellow background.
      </p>
    </div>
  );
};
export default HighlightSelectionExample;

Editor-change Event in React Quill

This one acts like a combination of both editor change and selection change. It's useful for watching both kinds of events. Here is a code example of it. In the example, we implement a simple word counter for React Quill:

 import React, { useRef, useEffect, useState } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
const SimpleWordCount: React.FC = () => {
  const quillRef = useRef<ReactQuill>(null);
  const [wordCount, setWordCount] = useState(0);
  useEffect(() => {
    const quill = quillRef.current?.getEditor();
    if (quill) {
      quill.on('editor-change', (eventName) => {
        if (eventName === 'text-change') {
          // Get plain text and calculate word count
          const text = quill.getText().trim();
          const words = text.length > 0 ? text.split(/\s+/) : [];
          setWordCount(words.length);
        }
      });
    }
    // Cleanup listener on component unmount
    return () => {
      quill?.off('editor-change');
    };
  }, []);
  return (
    <div>
      <ReactQuill ref={quillRef} />
      <p style={{ marginTop: '1rem' }}>
        <strong>Word Count:</strong> {wordCount}
      </p>
    </div>
  );
};
export default SimpleWordCount;

Content Manipulation in React Quill

React Quill can also be used to manipulate editor contents via the QUill API methods for content manipulations. The following table summarises the main methods used in these operations:

Quill MethodDescriptionExample

React Quill with Clipboard Module

In React Quill, you can access the clipboard module to control how copying, cutting, and pasting occur. This is an important module, especially when you have initial data in the database. When combined with clipboard matchers and custom blots, it becomes highly customizable. It works by converting HTML into Delta, as Quill only understands Delta and not HTML.

How Quill clipboard module works

When you paste HTML into the editor, Quill goes through the DOM elements one by one, converting them into Delta. It does this through its internal matchers. However, customization, allows you to define custom matchers that will be used to convert specific elements into Delta. This gives you control over which Delta is produced for certain HTML elements. This is particularly useful when dealing with custom blots.

Defining Quill Clipboard Matchers

We make use of the addMatcher function. Here is an example:

import React, { useRef, useEffect } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
const ClipboardSpanFormatter: React.FC = () => {
  const quillRef = useRef<ReactQuill>(null);
  useEffect(() => {
    const quill = quillRef.current?.getEditor();
    if (quill) {
      const clipboard = quill.getModule('clipboard');
      // Add a matcher specifically for `span` elements
      clipboard.addMatcher('SPAN', (node, delta) => {
        const plainText = node.textContent || '';
        return {
          ops: [
            { insert: '\n' }, // Add a newline before
            { insert: plainText, attributes: { color: 'pink', italic: true } }, // Add formatted text
            { insert: '\n' }, // Add a newline after
          ],
        };
      });
    }
  }, []);
  return (
    <div>
      <ReactQuill ref={quillRef} />
      <p style={{ marginTop: '1rem' }}>
        Paste any content with <code>&lt;span&gt;</code> tags here! It will be styled with pink italics and have new lines before and after the content.
      </p>
    </div>
  );
};
export default ClipboardSpanFormatter;

The above matcher applies bold and a specific color to all pasted content. With knowledge of delta, you can do much greater stuff.

React Quill with History Module

React Quill can take advantage of the undo and redo operations by tapping into the Quill.js history module. Here is an example:

import React, { useRef, useState, useEffect } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';
const UniqueHistoryExample: React.FC = () => {
  const quillRef = useRef<ReactQuill>(null);
  const [canUndo, setCanUndo] = useState(false);
  const [canRedo, setCanRedo] = useState(false);
  const [historySize, setHistorySize] = useState(0);
  useEffect(() => {
    const quill = quillRef.current?.getEditor();
    if (quill) {
      // Listen to history change (undo/redo state)
      quill.on('text-change', () => {
        // Update undo/redo capabilities and history size
        setCanUndo(quill.history.hasUndo());
        setCanRedo(quill.history.hasRedo());
        setHistorySize(quill.history.stack.length);
      });
    }
    // Cleanup listener on component unmount
    return () => {
      const quill = quillRef.current?.getEditor();
      quill?.off('text-change');
    };
  }, []);
  const handleUndo = () => {
    const quill = quillRef.current?.getEditor();
    if (quill && canUndo) {
      quill.history.undo();
    }
  };
  const handleRedo = () => {
    const quill = quillRef.current?.getEditor();
    if (quill && canRedo) {
      quill.history.redo();
    }
  };
  return (
    <div>
      <div style={{ marginBottom: '1rem', display: 'flex', gap: '1rem' }}>
        <button 
          onClick={handleUndo} 
          disabled={!canUndo} 
          style={{ 
            padding: '10px 20px', 
            backgroundColor: canUndo ? '#4CAF50' : '#ccc', 
            color: '#fff', 
            border: 'none', 
            cursor: canUndo ? 'pointer' : 'not-allowed', 
            borderRadius: '4px',
            transition: 'background-color 0.3s'
          }}
        >
          Undo
        </button>
        <button 
          onClick={handleRedo} 
          disabled={!canRedo} 
          style={{ 
            padding: '10px 20px', 
            backgroundColor: canRedo ? '#2196F3' : '#ccc', 
            color: '#fff', 
            border: 'none', 
            cursor: canRedo ? 'pointer' : 'not-allowed', 
            borderRadius: '4px',
            transition: 'background-color 0.3s'
          }}
        >
          Redo
        </button>
      </div>
      
      <div style={{ marginBottom: '1rem' }}>
        <p>
          <strong>History Stack Size:</strong> {historySize}
        </p>
        <p>
          <strong>Undo:</strong> {canUndo ? 'Enabled' : 'Disabled'}
        </p>
        <p>
          <strong>Redo:</strong> {canRedo ? 'Enabled' : 'Disabled'}
        </p>
      </div>
      <ReactQuill ref={quillRef} />
    </div>
  );
};
export default UniqueHistoryExample;

Designing in delta format in React Quill

Delta is the format that Quill.js uses to internally represent editor content. It is JSON-based, making it predictable and flexible.

Why use Delta

  • Predictability: Delta is predictable and consistent.
  • JSON-Based: Most developers are already familiar with JSON, so Delta is easy to understand.

Delta Operations in REact Quill

  • Insert: Adds the specified content at the current position.
  • Delete: Specifies how much text to delete.
  • Retain: Primarily used to apply formatting to a range without changing the content.
  • Attributes: Used to apply attributes to content. This is especially useful in custom blots.

Delta Constructor

The Delta constructor is used to create Delta objects more easily. It allows you to chain the operations add, retain, and delete. The example below shows how to use the Delta constructor methods:

  // Create Delta object with all operations
    const delta = new Quill.imports.delta()
      // Insert plain text
      .insert('Hello, world! ')
      // Insert formatted text (bold and italic)
      .insert('This is bold and italic text. ', { bold: true, italic: true })
      // Retain some text but apply color
      .retain(5, { color: 'blue' })
      // Insert more text (with underline)
      .insert('More text with underline.', { underline: true })
      // Delete the first 5 characters ("Hello")
      .delete(5)
      // Insert more formatted text (red and italic)
      .insert('New insertion with red color and italic.', { color: 'red', italic: true })
      // Retain the last word and apply bold
      .retain(4, { bold: true })
      // Insert a line break
      .insert('\n')
      // Insert some more text
      .insert('This is more text after a newline.')
      // Insert a final formatted text (green and italic)
      .insert('Final formatted text.', { color: 'green', italic: true }); 

Why Delta Constructor is better

  1. Cleaner Code: The Delta constructor produces cleaner code compared to manually creating a Delta object.
  2. Concise Code: The Delta constructor results in more concise code than manually using the Delta object.

Introduction to parchment

Parchment is a library that Quill relies on for the underlying structures. It handles the data model for the content being edited. Quill describes changes to the editor using Delta, which is then translated into Parchment’s blot format to create a blot tree. Since this is not a Parchment tutorial, we will only provide an example of a custom blot.

Custom Blot Example

mport Quill from 'quill';
import Parchment from 'parchment';
// Create a custom blot for styling text with a blue color
class BlueSpanBlot extends Parchment.Leaf {
  static blotName = 'blue-span'; // Internal name of the blot
  static tagName = 'span'; // HTML tag used for this blot
  static create(value) {
    let node = super.create();
    node.style.color = value || 'blue'; // Apply the color style
    return node;
  }
  static formats(node) {
    return node.style.color === 'blue' ? true : undefined;
  }
}
// Register the custom BlueSpanBlot with Quill
Quill.register(BlueSpanBlot);

Conclusion

In this tutorial, we have explored how React Quill works in depth. We’ve delved into how to customize it and how it integrates with its engine, Quill.js.

With this knowledge, you are now ready to create your own customized React Quill Editor.

Happy Editing!

References

Background References

  1. (January 1, 2025). Parchment documentation. *GitHub*. Retrieved January 1, 2025 from https://github.com/quilljs/parchment
  2. (January 2, 2025). Delta Format. *Quill.js*. Retrieved January 2, 2025 from https://quilljs.com/docs/delta
  3. Amaro, Z. (March 2, 2023). ReactQuill. *Quill.js*. Retrieved March 2, 2023 from https://www.npmjs.com/package/react-quill
  4. Amaro, Z. (August 4, 2017). React Quill. *GitHub*. Retrieved August 4, 2017 from https://github.com/zenoamaro/react-quill

About the Author

Joseph Horace's photo

Joseph Horace

Horace is a dedicated software developer with a deep passion for technology and problem-solving. With years of experience in developing robust and scalable applications, Horace specializes in building user-friendly solutions using cutting-edge technologies. His expertise spans across multiple areas of software development, with a focus on delivering high-quality code and seamless user experiences. Horace believes in continuous learning and enjoys sharing insights with the community through contributions and collaborations. When not coding, he enjoys exploring new technologies and staying updated on industry trends.

logo
Basic Utils

simplify and inspire technology

©2024, basicutils.com