Skip to content

Commit 862f468

Browse files
authored
Merge pull request #51 from react-restart/imperative-observer
feat(useIntersectionObserver): add more performant overload
2 parents 5f95dd0 + 2fba7ba commit 862f468

File tree

2 files changed

+74
-11
lines changed

2 files changed

+74
-11
lines changed

src/useIntersectionObserver.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,61 @@ import { useState } from 'react'
22

33
import useStableMemo from './useStableMemo'
44
import useEffect from './useIsomorphicEffect'
5+
import useEventCallback from './useEventCallback'
56

67
/**
78
* Setup an [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) on
8-
* a DOM Element.
9+
* a DOM Element that returns it's entries as they arrive.
910
*
1011
* @param element The DOM element to observe
1112
* @param init IntersectionObserver options
1213
*/
13-
export default function useIntersectionObserver<TElement extends Element>(
14+
function useIntersectionObserver<TElement extends Element>(
1415
element: TElement | null | undefined,
15-
{ threshold, root, rootMargin }: IntersectionObserverInit = {},
16-
) {
16+
options: IntersectionObserverInit,
17+
): IntersectionObserverEntry[]
18+
/**
19+
* Setup an [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) on
20+
* a DOM Element. This overload does not trigger component updates when receiving new
21+
* entries. This allows for finer grained performance optimizations by the consumer.
22+
*
23+
* @param element The DOM element to observe
24+
* @param callback A listener for intersection updates.
25+
* @param init IntersectionObserver options
26+
*/
27+
function useIntersectionObserver<TElement extends Element>(
28+
element: TElement | null | undefined,
29+
callback: IntersectionObserverCallback,
30+
options: IntersectionObserverInit,
31+
): void
32+
function useIntersectionObserver<TElement extends Element>(
33+
element: TElement | null | undefined,
34+
callbackOrOptions: IntersectionObserverCallback | IntersectionObserverInit,
35+
maybeOptions?: IntersectionObserverInit,
36+
): void | IntersectionObserverEntry[] {
37+
let callback: IntersectionObserverCallback | undefined
38+
let options: IntersectionObserverInit
39+
if (typeof callbackOrOptions === 'function') {
40+
callback = callbackOrOptions
41+
options = maybeOptions || {}
42+
} else {
43+
options = callbackOrOptions || {}
44+
}
45+
const { threshold, root, rootMargin } = options
1746
const [entries, setEntry] = useState<IntersectionObserverEntry[] | null>(null)
1847

48+
const handler = useEventCallback(callback || setEntry)
49+
1950
const observer = useStableMemo(
2051
() =>
2152
typeof IntersectionObserver !== 'undefined' &&
22-
new IntersectionObserver(entries => setEntry(entries), {
53+
new IntersectionObserver(handler, {
2354
threshold,
2455
root,
2556
rootMargin,
2657
}),
2758

28-
[root, rootMargin, threshold && JSON.stringify(threshold)],
59+
[handler, root, rootMargin, threshold && JSON.stringify(threshold)],
2960
)
3061

3162
useEffect(() => {
@@ -38,5 +69,7 @@ export default function useIntersectionObserver<TElement extends Element>(
3869
}
3970
}, [observer, element])
4071

41-
return entries || []
72+
return callback ? undefined : entries || []
4273
}
74+
75+
export default useIntersectionObserver

src/useMutationObserver.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import useCustomEffect from './useCustomEffect'
22
import { dequal } from 'dequal'
33
import useImmediateUpdateEffect from './useImmediateUpdateEffect'
44
import useEventCallback from './useEventCallback'
5+
import { useState } from 'react'
56

67
type Deps = [Element | null | undefined, MutationObserverInit]
78

@@ -16,7 +17,7 @@ function isDepsEqual(
1617
* Observe mutations on a DOM node or tree of DOM nodes.
1718
* Depends on the `MutationObserver` api.
1819
*
19-
* ```ts
20+
* ```tsx
2021
* const [element, attachRef] = useCallbackRef(null);
2122
*
2223
* useMutationObserver(element, { subtree: true }, (records) => {
@@ -36,8 +37,35 @@ function useMutationObserver(
3637
element: Element | null | undefined,
3738
config: MutationObserverInit,
3839
callback: MutationCallback,
39-
): void {
40-
const fn = useEventCallback(callback)
40+
): void
41+
/**
42+
* Observe mutations on a DOM node or tree of DOM nodes.
43+
* use a `MutationObserver` and return records as the are received.
44+
*
45+
* ```tsx
46+
* const [element, attachRef] = useCallbackRef(null);
47+
*
48+
* const records = useMutationObserver(element, { subtree: true });
49+
*
50+
* return (
51+
* <div ref={attachRef} />
52+
* )
53+
* ```
54+
*
55+
* @param element The DOM element to observe
56+
* @param config The observer configuration
57+
*/
58+
function useMutationObserver(
59+
element: Element | null | undefined,
60+
config: MutationObserverInit,
61+
): MutationRecord[]
62+
function useMutationObserver(
63+
element: Element | null | undefined,
64+
config: MutationObserverInit,
65+
callback?: MutationCallback,
66+
): MutationRecord[] | void {
67+
const [records, setRecords] = useState<MutationRecord[] | null>(null)
68+
const handler = useEventCallback(callback || setRecords)
4169

4270
useCustomEffect(
4371
() => {
@@ -47,7 +75,7 @@ function useMutationObserver(
4775
// observing again _should_ disable the last listener but doesn't
4876
// seem to always be the case, maybe just in JSDOM? In any case the cost
4977
// to redeclaring it is gonna be fairly low anyway, so make it simple
50-
const observer = new MutationObserver(fn)
78+
const observer = new MutationObserver(handler)
5179

5280
observer.observe(element, config)
5381

@@ -63,6 +91,8 @@ function useMutationObserver(
6391
effectHook: useImmediateUpdateEffect,
6492
},
6593
)
94+
95+
return callback ? void 0 : records || []
6696
}
6797

6898
export default useMutationObserver

0 commit comments

Comments
 (0)