498

useMutationObserver

Hook that gives you mutation observer state

sensorslowtest coverage
Draft
reactuse is a collection of essential React hooks. Start typing here and watch the stats update as the content changes.
20 words119 characters1 min read
import type { ClipboardEvent } from 'react';

import { useMutationObserver } from '@siberiacancode/reactuse';
import { FileTextIcon } from 'lucide-react';
import { useState } from 'react';

const INITIAL_TEXT =
  'reactuse is a collection of essential React hooks. Start typing here and watch the stats update as the content changes.';

const getStats = (text: string) => {
  const normalizedText = text.replace(/\s+/g, ' ').trim();
  const words = normalizedText.match(/[\p{L}\p{N}]+(?:['’-][\p{L}\p{N}]+)*/gu)?.length ?? 0;
  const characters = Array.from(text).length;
  const minutes = Math.max(1, Math.ceil(words / 200));
  return { words, characters, minutes };
};

const Demo = () => {
  const [stats, setStats] = useState(() => getStats(INITIAL_TEXT));

  const editor = useMutationObserver<HTMLDivElement>({
    childList: true,
    subtree: true,
    characterData: true,
    onChange: () => {
      const text = editor.ref.current?.textContent ?? '';
      setStats(getStats(text));
    }
  });

  const onPaste = (event: ClipboardEvent) => {
    event.preventDefault();
    const text = event.clipboardData.getData('text/plain');
    document.execCommand('insertText', false, text);
  };

  return (
    <section className='flex w-full max-w-md flex-col gap-3 p-4'>
      <div className='text-muted-foreground flex items-center gap-1.5 text-xs font-medium tracking-wide uppercase'>
        <FileTextIcon className='size-3.5' />
        Draft
      </div>

      <div
        contentEditable
        suppressContentEditableWarning
        ref={editor.ref}
        className='text-foreground min-h-36 rounded-lg text-base leading-relaxed outline-none'
        onPaste={onPaste}
      >
        {INITIAL_TEXT}
      </div>

      <div className='border-border text-muted-foreground flex gap-5 border-t pt-3 text-xs'>
        <span>
          <span className='text-foreground font-semibold tabular-nums'>{stats.words}</span> words
        </span>
        <span>
          <span className='text-foreground font-semibold tabular-nums'>{stats.characters}</span>{' '}
          characters
        </span>
        <span>
          <span className='text-foreground font-semibold tabular-nums'>{stats.minutes}</span> min
          read
        </span>
      </div>
    </section>
  );
};

export default Demo;
This hook uses MutationObserver browser api to provide enhanced functionality. Make sure to check for compatibility with different browsers when using this api

Installation

pnpm add @siberiacancode/reactuse

Usage

const { observer, stop } = useMutationObserver(ref, { childList: true });
// or
const { ref, observer, stop } = useMutationObserver({ childList: true });
// or
const { ref, observer, stop } = useMutationObserver((mutations) => console.log(mutations));
// or
const { observer, stop } = useMutationObserver((mutations) => console.log(mutations), ref);

Type Declarations

import type { HookTarget } from '@/utils/helpers';

import type { StateRef } from '../useRefState/useRefState';

export type UseMutationObserverCallback = (
  mutations: MutationRecord[],
  observer: MutationObserver
) => void;

export interface UseMutationObserverOptions extends MutationObserverInit {
  /** The enabled state of the mutation observer */
  enabled?: boolean;
  /** The callback to execute when mutation is detected */
  onChange?: UseMutationObserverCallback;
}

export interface UseMutationObserverReturn {
  /** The mutation observer instance */
  observer?: MutationObserver;
}

export interface UseMutationObserver {
  <Target extends Element>(
    options?: UseMutationObserverOptions,
    target?: never
  ): UseMutationObserverReturn & { ref: StateRef<Target> };

  (target: HookTarget, options?: UseMutationObserverOptions): UseMutationObserverReturn;

  <Target extends Element>(
    callback: UseMutationObserverCallback,
    target?: never
  ): UseMutationObserverReturn & { ref: StateRef<Target> };

  (target: HookTarget, callback: UseMutationObserverCallback): UseMutationObserverReturn;
}

API

Parameters

NameTypeDefaultNote
targetHookTarget-The target element to observe
options.enabledbooleantrueThe enabled state of the mutation observer
options.onChangeUseMutationObserverCallback-The callback to execute when mutation is detected
options.attributesboolean-Set to true if mutations to target's attributes are to be observed
options.characterDataboolean-Set to true if mutations to target's data are to be observed
options.childListboolean-Set to true if mutations to target's children are to be observed
options.subtreeboolean-Set to true if mutations to not just target, but also target's descendants are to be observed

Returns

UseMutationObserverReturn

Parameters

NameTypeDefaultNote
options.enabledbooleantrueThe enabled state of the mutation observer
options.onChangeUseMutationObserverCallback-The callback to execute when mutation is detected
options.attributesboolean-Set to true if mutations to target's attributes are to be observed
options.characterDataboolean-Set to true if mutations to target's data are to be observed
options.childListboolean-Set to true if mutations to target's children are to be observed
options.subtreeboolean-Set to true if mutations to not just target, but also target's descendants are to be observed

Returns

UseMutationObserverReturn & { ref: StateRef<Target> }

Parameters

NameTypeDefaultNote
callbackUseMutationObserverCallback-The callback to execute when mutation is detected

Returns

UseMutationObserverReturn & { ref: StateRef<Target> }

Parameters

NameTypeDefaultNote
callbackUseMutationObserverCallback-The callback to execute when mutation is detected
targetHookTarget-The target element to observe

Returns

UseMutationObserverReturn

Contributors

ddebabin

Last updated on