
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
react-hook-quill
Advanced tools
React Hook Quill is a lightweight wrapper for Quill, the rich text editor that does not interfere with the design of either React or Quill.
React Hook Quill is a lightweight wrapper for Quill that does not interfere with the design of either React or Quill.
Quill is implemented without frameworks like React. To put it simply, this hook internally initializes Quill as an external system within React using useEffect and cleans it up during a re-render and the unmount phase.

npm install react-hook-quill
Delta with React using useQuill and usePersistentDeltaIn this case, user edits are outside of the React lifecycle. React doesn't track the Quill changes, but user edits are automatically retained.
import { memo, useRef } from 'react';
import 'quill/dist/quill.snow.css';
import { Delta } from 'quill';
import { useQuill, usePersistentDelta } from 'react-hook-quill';
// Use `memo` to avoid re-rendering when the parent component re-renders.
// This is for performance purposes only.
const Editor = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const { persistentDeltaSetting } = usePersistentDelta(
{
containerRef: ref,
options: {
theme: 'snow'
},
setup: (quill) => {
// Disable undo for the initial text (optional).
quill.history.clear()
}
},
// Set an initial Delta (optional).
new Delta().insert('Hello Quill')
);
useQuill({ setting: persistentDeltaSetting });
return (
<div ref={ref} />
);
});
Delta with React using useQuill and useSyncDeltauseSyncDelta automatically sets up the state of Delta with React.
Note that you may not really need to sync Delta with React in your application.
Syncing Delta triggers a re-render with every user's edit and it may become an overhead in some cases.
import { useRef } from 'react';
import { Delta } from 'quill';
import 'quill/dist/quill.snow.css';
import { useQuill, useSyncDelta } from 'react-hook-quill';
const Editor = () => {
const ref = useRef<HTMLDivElement>(null);
const { delta, syncDeltaSetting } = useSyncDelta(
{
containerRef: ref,
options: {
theme: 'snow'
},
setup: (quill) => {
// Disable undo for the initial text (optional).
quill.history.clear()
}
},
// Set an initial Delta (optional).
new Delta().insert('Hello Quill')
);
useQuill({
setting: syncDeltaSetting
});
return (
<>
<div ref={ref} />
<div>{JSON.stringify(delta)}</div>
</>
);
};
Delta is accessible via the return value of useQuill as a reference to the Quill instance.
...
const quillRef = useQuill({
setting: {
containerRef: ref
}
});
...
// This code must be inside useEffect or event handlers to avoid reading while rendering.
// See the pitfall of useRef: https://react.dev/reference/react/useRef#referencing-a-value-with-a-ref
const delta = quillRef.current?.editor.delta;
...
Quill API is fully accessible in the same way as mentioned above.
A type for the parameter of useQuill.
export type Setting<ModuleOption = unknown> = {
/**
* A div element to attach a Quill Editor to
*/
containerRef: React.RefObject<HTMLDivElement | null>;
/**
* Options for initializing a Quill instance
* See: https://quilljs.com/docs/configuration#options
*/
options?: SafeQuillOptions<ModuleOption>;
/**
* This function is executed only once when Quill is mounted.
* A common use case is setting up synchronization of the Delta stored on the React side when the Quill side changes.
* You can read or write a ref object inside.
*/
setup?: (quill: Quill) => void;
/**
* This function is executed only once when Quill is unmounted.
* You can read or write a ref object inside.
*/
cleanup?: (quill: Quill) => void;
}
It extends the type of modules in QuillOptions to explicitly specify its type using generics. It is originally Record<string, unknown>.
interface SafeQuillOptions<ModuleOption> extends QuillOptions {
modules?: Record<string, ModuleOption>;
}
useQuillIt initializes Quill in a React component after the DOM has been mounted.
| arg | type | |||
|---|---|---|---|---|
| 1 | { setting } | { setting: Setting } | required | See the section of setting. |
A reference to the Quill instance. Before it is instantiated, the ref points to null.
type: React.MutableRefObject<Quill | null>
usePersistentDelta| arg | type | |||
|---|---|---|---|---|
| 1 | setting | Setting | required | See the section of setting. |
| 2 | initialDelta | Delta | optional | The default value is new Delta() |
type:
{
persistentDeltaSetting: Setting<unknown>;
updateSetting: (setting: Setting) => void;
}
| key | |
|---|---|
| persistentDeltaSetting | This is used for passing to useQuill. |
| updateSetting | Update Setting. It invokes a cleanup function of useQuill and creates a new Quill instance. |
useSyncDelta| arg | type | |||
|---|---|---|---|---|
| 1 | setting | Setting | required | See the section of setting. |
| 2 | initialDelta | Delta | optional | The default value is new Delta() |
type:
{
delta: Delta;
setDelta: React.Dispatch<React.SetStateAction<Delta>>;
syncDelta: (quill: Quill | null, delta: Delta) => void;
syncDeltaSetting: Setting<unknown>;
updateSetting: (setting: Setting) => void;
}
| key | |
|---|---|
| delta | A state of Delta on the React side. User edits are automatically synced. |
| setDelta | Minor use cases. Note that it changes the state of Delta only on the React side. Use syncDelta if you update both sides. |
| syncDelta | Change the Delta both on the React and Quill sides at once. |
| syncDeltaSetting | This is used for passing to useQuill. |
| updateSetting | Update Setting. It invokes a cleanup function of useQuill and creates a new Quill instance. |
Delta with Reactimport { memo, useRef } from 'react';
import { Delta } from 'quill';
import 'quill/dist/quill.snow.css';
import { useQuill } from 'react-hook-quill';
const Editor = memo(() => {
const ref = useRef<HTMLDivElement>(null);
// A reference to the delta that keeps user edits when the parent component re-renders.
const deltaRef = useRef<Delta | null>(null);
useQuill({
setting: {
containerRef: ref,
options: {
theme: 'snow'
},
setup: (quill) => {
// If previous user edits exist, set up the delta.
// You can read or write to the ref object because this function is called internally in `useEffect`.
// See the pitfall of useRef: https://react.dev/reference/react/useRef#referencing-a-value-with-a-ref
if (deltaRef.current) {
quill.setContents(deltaRef.current);
}
},
cleanup: (quill) => {
// Save user edits when it cleans up.
// It's the same as `setup`, you can read or write a ref object.
deltaRef.current = quill.editor.delta;
}
}
});
return (
<div ref={ref} />
);
});
import { memo, useRef } from 'react';
import 'quill/dist/quill.snow.css';
import { useQuill, usePersistentDelta } from 'react-hook-quill';
// https://quilljs.com/docs/modules/toolbar
const toolbarOptions = [
['bold', 'italic', 'underline', 'strike'],
['blockquote', 'code-block'],
['link', 'image', 'video', 'formula'],
[{ header: 1 }, { header: 2 }],
[{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }],
[{ script: 'sub' }, { script: 'super' }],
[{ indent: '-1' }, { indent: '+1' }],
[{ direction: 'rtl' }],
[{ size: ['small', false, 'large', 'huge'] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
[{ color: [] }, { background: [] }],
[{ font: [] }],
[{ align: [] }],
['clean']
];
const Editor = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const { persistentDeltaSetting } = usePersistentDelta({
containerRef: ref,
options: {
theme: 'snow',
placeholder: 'Enter some text...',
modules: {
toolbar: toolbarOptions
}
}
});
useQuill({ setting: persistentDeltaSetting });
return (
<div ref={ref} />
);
});
import { memo, useRef } from 'react';
import 'quill/dist/quill.snow.css';
import Quill, { Module } from 'quill';
import { useQuill, usePersistentDelta, Setting } from 'react-hook-quill';
interface CounterModuleOptions {
container: '#counter';
unit: 'word' | 'character';
}
// https://quilljs.com/docs/guides/building-a-custom-module
class Counter extends Module<CounterModuleOptions> {
public quill: Quill;
public options: CounterModuleOptions;
constructor (quill: Quill, options: CounterModuleOptions) {
super(quill, options);
this.quill = quill;
this.options = options;
quill.on(Quill.events.TEXT_CHANGE, this.update.bind(this));
}
calculate () {
const text = this.quill.getText();
if (this.options.unit === 'word') {
const trimmed = text.trim();
return trimmed.split(/\s+/).length;
} else {
return text.length;
}
}
update () {
const length = this.calculate();
let label = this.options.unit;
if (length !== 1) {
label += 's';
}
const container = document.querySelector(this.options.container);
if (container) {
container.textContent = `${length} ${label}`;
}
}
}
Quill.register('modules/counter', Counter);
const Editor = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const setting: Setting<CounterModuleOptions> = {
containerRef: ref,
options: {
theme: 'snow',
modules: {
counter: {
container: '#counter',
unit: 'character'
}
}
}
};
const { persistentDeltaSetting } = usePersistentDelta(setting);
useQuill({ setting: persistentDeltaSetting });
return (
<>
<div ref={ref} />
<div id='counter' />
</>
);
});
import { memo, useRef } from 'react';
import { useQuill, usePersistentDelta } from 'react-hook-quill';
import Quill from 'quill';
import { BlockEmbed } from 'quill/blots/block';
import 'quill/dist/quill.snow.css';
type DividerValue = 'blue' | 'red';
class DividerBlot extends BlockEmbed {
static blotName = 'divider';
static tagName = 'hr';
static create (value: DividerValue) {
const node = super.create(value);
if (node instanceof HTMLElement) {
node.setAttribute('style', `border: 1px solid ${value};`);
}
return node;
}
}
Quill.register(DividerBlot);
const Editor = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const { persistentDeltaSetting } = usePersistentDelta(
{
containerRef: ref,
options: {
theme: 'snow'
}
}
);
const quillRef = useQuill({ setting: persistentDeltaSetting });
return (
<>
<div ref={ref} />
<button onClick={() => {
const quill = quillRef.current;
if (quill) {
const range = quill.getSelection(true);
const dividerValue: DividerValue = 'blue';
quill.insertText(range.index, '\n', Quill.sources.USER);
quill.insertEmbed(range.index + 1, 'divider', dividerValue, Quill.sources.USER);
quill.setSelection(range.index + 2, Quill.sources.SILENT);
}
}}>
Add Divider
</button>
</>
);
});
import { memo, useRef, useState } from 'react';
import 'quill/dist/quill.snow.css';
import 'quill/dist/quill.bubble.css';
import { useQuill, usePersistentDelta } from 'react-hook-quill';
export const Editor = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const [theme, setTheme] = useState('snow');
const { persistentDeltaSetting, updateSetting } = usePersistentDelta({
containerRef: ref,
options: {
theme: 'snow'
}
});
useQuill({ setting: persistentDeltaSetting });
return (
<>
<div ref={ref} />
<button onClick={() => {
const nextTheme = theme === 'snow' ? 'bubble' : 'snow';
updateSetting({
containerRef: ref,
options: {
theme: nextTheme
}
});
setTheme(nextTheme);
}}>
Change the theme
</button>
</>
);
});
FAQs
React Hook Quill is a lightweight wrapper for Quill, the rich text editor that does not interfere with the design of either React or Quill.
We found that react-hook-quill demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.