React Quill with Quilljs and Example Codes
Table of Contents
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:
- Text-Change
- Selection-change
- 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 Method | Description | Example |
---|
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><span></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
- Cleaner Code: The Delta constructor produces cleaner code compared to manually creating a Delta object.
- 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
- (January 1, 2025). Parchment documentation. *GitHub*. Retrieved January 1, 2025 from https://github.com/quilljs/parchment
- (January 2, 2025). Delta Format. *Quill.js*. Retrieved January 2, 2025 from https://quilljs.com/docs/delta
- Amaro, Z. (March 2, 2023). ReactQuill. *Quill.js*. Retrieved March 2, 2023 from https://www.npmjs.com/package/react-quill
- Amaro, Z. (August 4, 2017). React Quill. *GitHub*. Retrieved August 4, 2017 from https://github.com/zenoamaro/react-quill
About the Author
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.