Skip to content

Commit 4a90bd5

Browse files
committed
observer cache improvement for using different observers on the same element
1 parent 3010199 commit 4a90bd5

File tree

6 files changed

+70
-36
lines changed

6 files changed

+70
-36
lines changed

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
# react-intersection-observer-hook
2+
3+
![Build status](https://img.shields.io/github/actions/workflow/status/onderonur/react-intersection-observer-hook/main.yml)
4+
![License](https://img.shields.io/npm/l/react-intersection-observer-hook)
5+
![Version](https://img.shields.io/npm/v/react-intersection-observer-hook)
6+
27
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
8+
39
[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat-square)](#contributors-)
10+
411
<!-- ALL-CONTRIBUTORS-BADGE:END -->
5-
![Build status](https://img.shields.io/github/workflow/status/onderonur/react-intersection-observer-hook/CI)
6-
![License](https://img.shields.io/npm/l/react-intersection-observer-hook)
7-
![Version](https://img.shields.io/npm/v/react-intersection-observer-hook)
812

913
This is a small React hook package to use [Insersection Observer](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) declaratively. By using this hook, you can easily track if a component is visible or not, create lazy loading images, trigger animations on entering or leaving the screen etc.
1014

@@ -128,4 +132,4 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
128132

129133
<!-- ALL-CONTRIBUTORS-LIST:END -->
130134

131-
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
135+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

example/components/Message.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ function Message({ label, isVisible }: MessageProps) {
2222
<MessageLabel>{label}:</MessageLabel>
2323
{isVisible
2424
? '(づ。◕‿‿◕。)づ You have found it!'
25-
: "¯\\_(ツ)_/¯ I don't know where the green ball is. Use scroll to find it."}
25+
: `¯\\_(ツ)_/¯ I don't know where it is. Use scroll to find it.`}
2626
</MessageContent>
2727
);
2828
}

example/index.tsx

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@ const Content = styled.div`
3838
height: 3000px;
3939
`;
4040

41-
const Ball = styled.div`
41+
const Ball = styled.div<{ color: string }>`
4242
width: 100px;
4343
height: 100px;
4444
border-radius: 50%;
45-
background-color: #1db954;
45+
background-color: ${({ color }) => color};
4646
margin-left: auto;
4747
margin-right: auto;
4848
margin-top: 50%;
@@ -57,29 +57,49 @@ enum ParentType {
5757
function App() {
5858
const [isContentVisible, setIsContentVisible] = React.useState(true);
5959
const [mode, setMode] = React.useState(ParentType.DOCUMENT);
60-
const [ref, { isVisible, rootRef }] = useTrackVisibility();
6160
const [
62-
ref2,
63-
{ isVisible: isVisible2, rootRef: rootRef2 },
61+
firstBallRef,
62+
{ isVisible: isFirstBallVisible, rootRef: firstBallRootRef },
6463
] = useTrackVisibility();
6564

65+
const [
66+
secondBallRef1,
67+
{ isVisible: isSecondBallVisible1, rootRef: secondBallRootRef1 },
68+
] = useTrackVisibility();
69+
70+
const [
71+
secondBallRef2,
72+
{ isVisible: isSecondBallVisible2, rootRef: secondBallRootRef2 },
73+
] = useTrackVisibility({
74+
threshold: 0.5,
75+
});
76+
77+
const secondBallRef = React.useCallback(
78+
(node: HTMLDivElement) => {
79+
secondBallRef1(node);
80+
secondBallRef2(node);
81+
},
82+
[secondBallRef1, secondBallRef2],
83+
);
84+
6685
const content = (
6786
<>
6887
<Content>
69-
<Ball ref={ref} />
88+
<Ball ref={firstBallRef} color="#1db954" />
7089
</Content>
7190
<Content>
72-
<Ball ref={ref2} />
91+
<Ball ref={secondBallRef} color="#f20404" />
7392
</Content>
7493
</>
7594
);
7695

7796
const rootCallback = React.useCallback(
7897
(node) => {
79-
rootRef(node);
80-
rootRef2(node);
98+
firstBallRootRef(node);
99+
secondBallRootRef1(node);
100+
secondBallRootRef2(node);
81101
},
82-
[rootRef, rootRef2],
102+
[firstBallRootRef, secondBallRootRef1, secondBallRootRef2],
83103
);
84104

85105
return (
@@ -107,8 +127,12 @@ function App() {
107127
/>
108128
Show Content
109129
</Label>
110-
<Message label="First ball" isVisible={isVisible} />
111-
<Message label="Second ball" isVisible={isVisible2} />
130+
<Message label="Green ball" isVisible={isFirstBallVisible} />
131+
<Message label="Red ball" isVisible={isSecondBallVisible1} />
132+
<Message
133+
label="More than half of red ball"
134+
isVisible={isSecondBallVisible2}
135+
/>
112136
</Top>
113137
{isContentVisible && (
114138
<div>

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-intersection-observer-hook",
3-
"version": "2.1.0",
3+
"version": "2.1.1",
44
"license": "MIT",
55
"author": "onderonur",
66
"main": "dist/index.js",

src/utils.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
type ObserverCache = Map<string, IntersectionObserver>;
1+
type EntryCallback = (entry: IntersectionObserverEntry) => void;
2+
3+
type CachedObserver = {
4+
observer: IntersectionObserver;
5+
entryCallbacks: Map<Element, EntryCallback>;
6+
};
7+
8+
type ObserverCache = Map<string, CachedObserver>;
29

310
type ObserverCachesByRoot = Map<
411
IntersectionObserverInit['root'],
@@ -16,28 +23,25 @@ export type CachedIntersectionObserver = {
1623
export function createObserverCache() {
1724
const cachesByRoot: ObserverCachesByRoot = new Map();
1825

19-
const entryCallbacks = new Map<
20-
Element,
21-
(entry: IntersectionObserverEntry) => void
22-
>();
23-
2426
function getObserver({
2527
root,
2628
rootMargin,
2729
threshold,
2830
}: IntersectionObserverInit): CachedIntersectionObserver {
29-
let cacheByRoot: ObserverCache | undefined = cachesByRoot.get(root);
31+
let cacheByRoot = cachesByRoot.get(root);
3032

3133
if (!cacheByRoot) {
32-
cacheByRoot = new Map<string, IntersectionObserver>();
34+
cacheByRoot = new Map();
3335
cachesByRoot.set(root, cacheByRoot);
3436
}
3537

3638
const cacheKey = JSON.stringify({ rootMargin, threshold });
37-
let observer = cacheByRoot.get(cacheKey);
39+
let cachedObserver = cacheByRoot.get(cacheKey);
40+
41+
if (!cachedObserver) {
42+
const entryCallbacks = new Map<Element, EntryCallback>();
3843

39-
if (!observer) {
40-
observer = new IntersectionObserver(
44+
const observer = new IntersectionObserver(
4145
(entries) => {
4246
entries.forEach((entry) => {
4347
const callback = entryCallbacks.get(entry.target);
@@ -47,20 +51,22 @@ export function createObserverCache() {
4751
{ root, rootMargin, threshold },
4852
);
4953

50-
cacheByRoot.set(cacheKey, observer);
54+
cachedObserver = { observer, entryCallbacks };
55+
56+
cacheByRoot.set(cacheKey, cachedObserver);
5157
}
5258

5359
return {
5460
observe: (
5561
node: Element,
5662
callback: (entry: IntersectionObserverEntry) => void,
5763
) => {
58-
entryCallbacks.set(node, callback);
59-
observer?.observe(node);
64+
cachedObserver?.entryCallbacks.set(node, callback);
65+
cachedObserver?.observer.observe(node);
6066
},
6167
unobserve: (node: Element) => {
62-
entryCallbacks.delete(node);
63-
observer?.unobserve(node);
68+
cachedObserver?.entryCallbacks.delete(node);
69+
cachedObserver?.observer.unobserve(node);
6470
},
6571
};
6672
}

0 commit comments

Comments
 (0)