1.0.7 • Published 4 months ago

react-hook-quill v1.0.7

Weekly downloads
-
License
MIT
Repository
github
Last release
4 months ago

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'
      }
    },
    // Set an initial Delta (optional).
    new Delta().insert('Hello Quill')
  );

  useQuill({ setting: persistentDeltaSetting });

  return (
    <div ref={ref} />
  );
});

Reference

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'
      }
    },
    // Set an initial Delta (optional).
    new Delta().insert('Hello Quill')
  );

  useQuill({
    setting: syncDeltaSetting
  });

  return (
    <>
      <div ref={ref} />
      <div>{JSON.stringify(delta)}</div>
    </>
  );
};

Reference

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

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

Hooks

useQuill

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

Parameters

argtype
1{ setting }{ setting: Setting }requiredSee 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

argtype
1settingSettingrequiredSee the section of setting.
2initialDeltaDeltaoptionalThe default value is new Delta()

Returns

type:

{
    persistentDeltaSetting: Setting<unknown>;
    updateSetting: (setting: Setting) => void;
}
key
persistentDeltaSettingThis is used for passing to useQuill.
updateSettingUpdate Setting. It invokes a cleanup function of useQuill and creates a new Quill instance.

useSyncDelta

Parameters

argtype
1settingSettingrequiredSee the section of setting.
2initialDeltaDeltaoptionalThe 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
deltaA state of Delta on the React side. User edits are automatically synced.
setDeltaMinor use cases. Note that it changes the state of Delta only on the React side. Use syncDelta if you update both sides.
syncDeltaChange the Delta both on the React and Quill sides at once.
syncDeltaSettingThis is used for passing to useQuill.
updateSettingUpdate 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

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

4 months ago

1.0.6

5 months ago

1.0.5

5 months ago

1.0.4

5 months ago

1.0.3

5 months ago

1.0.2

5 months ago

1.0.1

5 months ago

1.0.0

5 months ago