Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions src/useIntersectionObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,61 @@ import { useState } from 'react'

import useStableMemo from './useStableMemo'
import useEffect from './useIsomorphicEffect'
import useEventCallback from './useEventCallback'

/**
* Setup an [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) on
* a DOM Element.
* a DOM Element that returns it's entries as they arrive.
*
* @param element The DOM element to observe
* @param init IntersectionObserver options
*/
export default function useIntersectionObserver<TElement extends Element>(
function useIntersectionObserver<TElement extends Element>(
element: TElement | null | undefined,
{ threshold, root, rootMargin }: IntersectionObserverInit = {},
) {
options: IntersectionObserverInit,
): IntersectionObserverEntry[]
/**
* Setup an [`IntersectionObserver`](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) on
* a DOM Element. This overload does not trigger component updates when receiving new
* entries. This allows for finer grained performance optimizations by the consumer.
*
* @param element The DOM element to observe
* @param callback A listener for intersection updates.
* @param init IntersectionObserver options
*/
function useIntersectionObserver<TElement extends Element>(
element: TElement | null | undefined,
callback: IntersectionObserverCallback,
options: IntersectionObserverInit,
): void
function useIntersectionObserver<TElement extends Element>(
element: TElement | null | undefined,
callbackOrOptions: IntersectionObserverCallback | IntersectionObserverInit,
maybeOptions?: IntersectionObserverInit,
): void | IntersectionObserverEntry[] {
let callback: IntersectionObserverCallback | undefined
let options: IntersectionObserverInit
if (typeof callbackOrOptions === 'function') {
callback = callbackOrOptions
options = maybeOptions || {}
} else {
options = callbackOrOptions || {}
}
const { threshold, root, rootMargin } = options
const [entries, setEntry] = useState<IntersectionObserverEntry[] | null>(null)

const handler = useEventCallback(callback || setEntry)

const observer = useStableMemo(
() =>
typeof IntersectionObserver !== 'undefined' &&
new IntersectionObserver(entries => setEntry(entries), {
new IntersectionObserver(handler, {
threshold,
root,
rootMargin,
}),

[root, rootMargin, threshold && JSON.stringify(threshold)],
[handler, root, rootMargin, threshold && JSON.stringify(threshold)],
)

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

return entries || []
return callback ? undefined : entries || []
}

export default useIntersectionObserver
38 changes: 34 additions & 4 deletions src/useMutationObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import useCustomEffect from './useCustomEffect'
import { dequal } from 'dequal'
import useImmediateUpdateEffect from './useImmediateUpdateEffect'
import useEventCallback from './useEventCallback'
import { useState } from 'react'

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

Expand All @@ -16,7 +17,7 @@ function isDepsEqual(
* Observe mutations on a DOM node or tree of DOM nodes.
* Depends on the `MutationObserver` api.
*
* ```ts
* ```tsx
* const [element, attachRef] = useCallbackRef(null);
*
* useMutationObserver(element, { subtree: true }, (records) => {
Expand All @@ -36,8 +37,35 @@ function useMutationObserver(
element: Element | null | undefined,
config: MutationObserverInit,
callback: MutationCallback,
): void {
const fn = useEventCallback(callback)
): void
/**
* Observe mutations on a DOM node or tree of DOM nodes.
* use a `MutationObserver` and return records as the are received.
*
* ```tsx
* const [element, attachRef] = useCallbackRef(null);
*
* const records = useMutationObserver(element, { subtree: true });
*
* return (
* <div ref={attachRef} />
* )
* ```
*
* @param element The DOM element to observe
* @param config The observer configuration
*/
function useMutationObserver(
element: Element | null | undefined,
config: MutationObserverInit,
): MutationRecord[]
function useMutationObserver(
element: Element | null | undefined,
config: MutationObserverInit,
callback?: MutationCallback,
): MutationRecord[] | void {
const [records, setRecords] = useState<MutationRecord[] | null>(null)
const handler = useEventCallback(callback || setRecords)

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

observer.observe(element, config)

Expand All @@ -63,6 +91,8 @@ function useMutationObserver(
effectHook: useImmediateUpdateEffect,
},
)

return callback ? void 0 : records || []
}

export default useMutationObserver