npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-hook-quill

v1.0.36

Published

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.

Readme

React Hook 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.

architecture

Quick Start

Install

npm install react-hook-quill

Non-state Control of Delta with React using useQuill and usePersistentDelta

In 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} />
  );
});

Reference

  • memo: https://react.dev/reference/react/memo

State Control of Delta with React using useQuill and useSyncDelta

useSyncDelta 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>
    </>
  );
};

Reference

  • Render and Commit: https://react.dev/learn/render-and-commit

Access to the Quill instance

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.

Reference

  • Delta: https://quilljs.com/docs/delta
  • Quill API: https://quilljs.com/docs/api
  • Pitfall of useRef: https://react.dev/reference/react/useRef#referencing-a-value-with-a-ref

Types and Interfaces

Setting

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;
}

SafeQuillOptions

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>;
}

Reference

  • QuillOptions: https://quilljs.com/docs/configuration#options
  • Modules: https://quilljs.com/docs/modules

Hooks

useQuill

It initializes Quill in a React component after the DOM has been mounted.

Parameters

| |arg |type | | | |-|------------|----------------------|---------|-----------------------------------------| |1|{ setting } |{ setting: Setting }|required | See the section of setting. |

Returns

A reference to the Quill instance. Before it is instantiated, the ref points to null.

type: React.MutableRefObject<Quill | null>


usePersistentDelta

Parameters

| |arg |type | | | |-|------------|---------|---------|----------------------------------------| |1|setting |Setting|required | See the section of setting.| |2|initialDelta|Delta |optional | The default value is new Delta() |

Returns

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

Parameters

| |arg |type | | | |-|------------|---------|---------|----------------------------------------| |1|setting |Setting|required | See the section of setting.| |2|initialDelta|Delta |optional | The default value is new Delta() |

Returns

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. |

More Examples

Non-state Control of Delta with React

import { 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} />
  );
});

Reference

  • memo: https://react.dev/reference/react/memo
  • Pitfall of useRef: https://react.dev/reference/react/useRef#referencing-a-value-with-a-ref

Configure options

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} />
  );
});

Configure modules

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' />
    </>
  );
});

Configure blots

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>
    </>
  );
});

Update settings

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>
    </>
  );
});