aboutsummaryrefslogtreecommitdiffstats
path: root/src/utils/hooks/use-mutation-observer
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils/hooks/use-mutation-observer')
-rw-r--r--src/utils/hooks/use-mutation-observer/index.ts1
-rw-r--r--src/utils/hooks/use-mutation-observer/use-mutation-observer.test.ts42
-rw-r--r--src/utils/hooks/use-mutation-observer/use-mutation-observer.ts35
3 files changed, 78 insertions, 0 deletions
diff --git a/src/utils/hooks/use-mutation-observer/index.ts b/src/utils/hooks/use-mutation-observer/index.ts
new file mode 100644
index 0000000..16780fe
--- /dev/null
+++ b/src/utils/hooks/use-mutation-observer/index.ts
@@ -0,0 +1 @@
+export * from './use-mutation-observer';
diff --git a/src/utils/hooks/use-mutation-observer/use-mutation-observer.test.ts b/src/utils/hooks/use-mutation-observer/use-mutation-observer.test.ts
new file mode 100644
index 0000000..62ed559
--- /dev/null
+++ b/src/utils/hooks/use-mutation-observer/use-mutation-observer.test.ts
@@ -0,0 +1,42 @@
+import { beforeEach, describe, expect, it, jest } from '@jest/globals';
+import { renderHook } from '@testing-library/react';
+import { useMutationObserver } from './use-mutation-observer';
+
+describe('useMutationObserver', () => {
+ beforeEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ it('can create a new observer', () => {
+ const callback = jest.fn();
+ const observerSpy = jest.spyOn(MutationObserver.prototype, 'observe');
+ const wrapper = document.createElement('div');
+ const options: MutationObserverInit = { childList: true };
+
+ renderHook(() =>
+ useMutationObserver({
+ callback,
+ options,
+ ref: { current: wrapper },
+ })
+ );
+
+ expect(observerSpy).toHaveBeenCalledTimes(1);
+ expect(observerSpy).toHaveBeenCalledWith(wrapper, options);
+ });
+
+ it('does not create a new observer when ref is null', () => {
+ const callback = jest.fn();
+ const observerSpy = jest.spyOn(MutationObserver.prototype, 'observe');
+
+ renderHook(() =>
+ useMutationObserver({
+ callback,
+ options: { childList: true },
+ ref: { current: null },
+ })
+ );
+
+ expect(observerSpy).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/utils/hooks/use-mutation-observer/use-mutation-observer.ts b/src/utils/hooks/use-mutation-observer/use-mutation-observer.ts
new file mode 100644
index 0000000..6043055
--- /dev/null
+++ b/src/utils/hooks/use-mutation-observer/use-mutation-observer.ts
@@ -0,0 +1,35 @@
+import { type RefObject, useEffect } from 'react';
+import type { Nullable } from '../../../types';
+
+type UseMutationObserverProps<T extends Nullable<HTMLElement>> = {
+ /**
+ * A callback to execute when mutations are observed.
+ */
+ callback: MutationCallback;
+ /**
+ * The options passed to mutation observer.
+ */
+ options: MutationObserverInit;
+ /**
+ * A reference to the DOM node to observe.
+ */
+ ref: RefObject<T>;
+};
+
+export const useMutationObserver = <T extends Nullable<HTMLElement>>({
+ callback,
+ options,
+ ref,
+}: UseMutationObserverProps<T>) => {
+ useEffect(() => {
+ if (!ref.current) return undefined;
+
+ const observer = new MutationObserver(callback);
+
+ observer.observe(ref.current, options);
+
+ return () => {
+ observer.disconnect();
+ };
+ }, [callback, options, ref]);
+};