Techcookies

Mutation observer API using ReactJS and TypeScript.

JavaScript, ReactJS, TypeScript | Tue Oct 01 2024 | 3 min read

We have learned about Mutation observer API in our last blog(https://techcookies.com/post/mutation-observer-api), you can refer for basics and details.
In this blog we'll learn how we can implement it in ReactJS using TypeScript.

Mutation observer is an built-in JavaScript API that watch for changes made to the DOM tree and fires a callback function when it detects a change.

Use cases

  1. Monitoring DOM changes: The Mutation Observer can be used to monitor changes to the DOM tree. Let's assume we are consuming product documentation from 3rd party vendor and we need to highlight certain element like header(h1, h2) element or code block(pre, code).
  2. Undo/Redo: The Mutation Observer can be used to implement an Undo/Redo stack in an application by observing data additions and removals
  3. Track injection attack: If any 3rd party try to mutate DOM, it can be tracked.
  4. Live Preview: If we have any MD or web editor and we need to show live preview.

Basic Implementation

  1. We can create one hook for Mutation observer API - useMutationObserver
typescript
import { useEffect, useRef } from 'react';
const options = {
  attributes: true,
  characterData: true,
  childList: true,
  subtree: true,
}

function useMutationObserver<T extends HTMLElement>(
  callback: (target: T, options: MutationRecord) => void
) {
  const ref = useRef<T>(null)

  useEffect(() => {
    const element = ref?.current;

    if (!element) {
      return;
    }

    const observer = new MutationObserver((entries: MutationRecord[]) => {
      callback(element, entries[0]);
    });

    observer.observe(element, options);
    return () => {
      observer.disconnect();
    };
  }, [callback, ref]);

  return ref
}

export default useMutationObserver;
  1. We can consume this custom hook in any react component.
typescript
"use client";
import { useCallback, useState } from "react";
import useMutationObserver from "../hooks/useMutationObserver";

const MutationObserverComp = () => {
  const [width, setWidth] = useState("");
  const [color, setColor] = useState("");
  const [content, setContent] = useState("");
  const onMutation = useCallback(
    (target: HTMLDivElement, entry: MutationRecord) => {
      if (entry.type === "attributes" && entry.attributeName === "style") {
        switch (entry.target.clientHeight + 2) {
          case 100:
           
           // setColor("red");
           target.style.backgroundColor = "white"
           target.style.color = 'red';
           target.textContent = "red";
            break;
          case 200:
            target.style.backgroundColor = "green"
            target.style.color = 'blue';
            target.textContent = "blue";
            break;
          case 300:
            target.style.backgroundColor = "red"
           target.style.color = 'white';
           target.textContent = "white";
            break;
          case 400:
            target.style.backgroundColor = "blue"
            target.style.color = 'yellow';
            target.textContent = "yellow";
            break;
          default:
            break;
        }
      }
    },
    []
  );
  const ref = useMutationObserver(onMutation);
  const clickHandler100 = () => {
    setWidth("100px");
  };
  const clickHandler200 = () => {
    setWidth("200px");
  };
  const clickHandler300 = () => {
    setWidth("300px");
  };
  const clickHandler400 = () => {
    setWidth("400px");
  };

  return (
    <div>
      <div
        className="target-element"
        ref={ref}
        contentEditable
        suppressContentEditableWarning={true}
        style={{
          width: width,
          height: width,
          backgroundColor: color,
          border: "solid 1px black",
        }}
      >
        {content}
      </div>
      <button
        style={{ border: "solid 1px black", padding: "10px", margin: "10px" }}
        onClick={clickHandler100}
      >
        100
      </button>
      <button
        style={{ border: "solid 1px black", padding: "10px", margin: "10px" }}
        onClick={clickHandler200}
      >
        200
      </button>
      <button
        style={{ border: "solid 1px black", padding: "10px", margin: "10px" }}
        onClick={clickHandler300}
      >
        300
      </button>
      <button
        style={{ border: "solid 1px black", padding: "10px", margin: "10px" }}
        onClick={clickHandler400}
      >
        400
      </button>
    </div>
  );
};
export default MutationObserverComp;

Preview

alt text