Skip to content

Commit 23cea10

Browse files
committed
fix(getPooled): Fixes a problem where threshold options could wrongly match on diff array lengths
1 parent 1edb1f0 commit 23cea10

File tree

6 files changed

+59
-58
lines changed

6 files changed

+59
-58
lines changed

src/IntersectionObserver.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { findDOMNode } from 'react-dom';
33
import PropTypes from 'prop-types';
44
import invariant from 'invariant';
55
import IntersectionObserverContainer from './IntersectionObserverContainer';
6-
import { isDOMTypeElement } from './utils';
6+
import { isDOMTypeElement, shallowCompareOptions } from './utils';
77

88
/**
99
* The Intersection Observer API callback that is called whenever one element,
@@ -22,15 +22,6 @@ export function callback(changes, observer) {
2222
});
2323
}
2424

25-
export function shallowCompareOptions(next, prev) {
26-
if (Array.isArray(next) && Array.isArray(prev)) {
27-
if (next.length === prev.length) {
28-
return next.some((_, index) => shallowCompareOptions(next[index], prev[index]));
29-
}
30-
}
31-
return next !== prev;
32-
}
33-
3425
const observerOptions = ['root', 'rootMargin', 'threshold'];
3526
const objectProto = Object.prototype;
3627

src/IntersectionObserverContainer.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
1-
import { parseRootMargin } from './utils';
1+
import { parseRootMargin, shallowCompareOptions } from './utils';
22

33
export function getPooled(options = {}) {
4-
let matchCount = 0;
54
const root = options.root || null;
65
const rootMargin = parseRootMargin(options.rootMargin);
76
const threshold = Array.isArray(options.threshold)
87
? options.threshold
98
: [typeof options.threshold !== 'undefined' ? options.threshold : 0];
109
// eslint-disable-next-line no-restricted-syntax
1110
for (const observer of storage.keys()) {
12-
let thresholdMatches = threshold;
13-
let thresholds = observer.thresholds;
14-
if (threshold.length > observer.thresholds) {
15-
thresholdMatches = observer.thresholds;
16-
thresholds = threshold;
17-
}
18-
matchCount += thresholds.every(v => v === thresholdMatches[thresholdMatches.indexOf(v)]);
19-
matchCount += root === observer.root;
20-
matchCount += rootMargin === observer.rootMargin;
21-
if (matchCount === 3) {
11+
const unmatched = [
12+
[root, observer.root],
13+
[rootMargin, observer.rootMargin],
14+
[threshold, observer.thresholds],
15+
].some(option => shallowCompareOptions(...option));
16+
17+
if (!unmatched) {
2218
return observer;
2319
}
2420
}

src/utils.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,12 @@ export function parseRootMargin(rootMargin) {
2121

2222
return margins.join(' ');
2323
}
24+
25+
export function shallowCompareOptions(next, prev) {
26+
if (Array.isArray(next) && Array.isArray(prev)) {
27+
if (next.length === prev.length) {
28+
return next.some((_, index) => shallowCompareOptions(next[index], prev[index]));
29+
}
30+
}
31+
return next !== prev;
32+
}

test/IntersectionObserver.test.js

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -239,41 +239,6 @@ describe('update', () => {
239239
});
240240
});
241241

242-
describe('shallowCompareOptions', () => {
243-
const comparerFn = (nextProps, prevProps) =>
244-
['disabled', 'root', 'rootMargin', 'threshold'].some(option =>
245-
shallowCompareOptions(nextProps[option], prevProps[option])
246-
);
247-
248-
test('should return true if threshold array length is not the same', () => {
249-
const nextProps = { threshold: [0.25, 0.5] };
250-
const prevProps = { threshold: [0.25, 0.5, 0.75] };
251-
252-
expect(comparerFn(nextProps, prevProps)).toBeTruthy();
253-
});
254-
255-
test('should return true if threshold array length is the same but not equal', () => {
256-
const nextProps = { threshold: [0.25, 0.75, 0.5] };
257-
const prevProps = { threshold: [0.25, 0.5, 0.75] };
258-
259-
expect(comparerFn(nextProps, prevProps)).toBeTruthy();
260-
});
261-
262-
test('should return false if options are equal', () => {
263-
const nextProps = { disabled: true, root: 1, rootMargin: 2, threshold: [0.25, 0.75, 0.5] };
264-
const prevProps = { ...nextProps };
265-
266-
expect(comparerFn(nextProps, prevProps)).toBeFalsy();
267-
});
268-
269-
test('should return true if options are different', () => {
270-
const nextProps = { disabled: true, root: 1, rootMargin: 2, threshold: [0.25, 0.75, 0.5] };
271-
const prevProps = { ...nextProps, threshold: 1 };
272-
273-
expect(comparerFn(nextProps, prevProps)).toBeTruthy();
274-
});
275-
});
276-
277242
describe('callback', () => {
278243
test('should call propType onChange for each of the changes', () => {
279244
const spy = jest.fn();

test/IntersectionObserverContainer.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ describe('#getPooled', () => {
4747
expect(getPooled({ threshold: 0.5 })).toBeNull();
4848
});
4949

50+
test('returns nothing given threshold did not match', () => {
51+
expect(getPooled({ rootMargin: '-10% 0%', threshold: [0, 0.5, 1, 0.25] })).toBeNull();
52+
expect(getPooled({ rootMargin: '-10% 0%', threshold: [1, 0.5, 0] })).toBeNull();
53+
});
54+
5055
test('throws if rootMargin cannot be parsed', () => {
5156
expect(() => getPooled({ rootMargin: '-10% 0', threshold: 0 })).toThrow(
5257
'rootMargin must be specified in pixels or percent'

test/utils.test.js

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-env jest */
2-
import { parseRootMargin } from '../src/utils';
2+
import { parseRootMargin, shallowCompareOptions } from '../src/utils';
33

44
describe('parseRootMargin', () => {
55
test('throws when using wrong units', () => {
@@ -13,3 +13,38 @@ describe('parseRootMargin', () => {
1313
expect(parseRootMargin('10px 5px 0%')).toBe('10px 5px 0% 5px');
1414
});
1515
});
16+
17+
describe('shallowCompareOptions', () => {
18+
const comparerFn = (nextProps, prevProps) =>
19+
['disabled', 'root', 'rootMargin', 'threshold'].some(option =>
20+
shallowCompareOptions(nextProps[option], prevProps[option])
21+
);
22+
23+
test('should return true if threshold array length is not the same', () => {
24+
const nextProps = { threshold: [0.25, 0.5] };
25+
const prevProps = { threshold: [0.25, 0.5, 0.75] };
26+
27+
expect(comparerFn(nextProps, prevProps)).toBeTruthy();
28+
});
29+
30+
test('should return true if threshold array length is the same but not equal', () => {
31+
const nextProps = { threshold: [0.25, 0.75, 0.5] };
32+
const prevProps = { threshold: [0.25, 0.5, 0.75] };
33+
34+
expect(comparerFn(nextProps, prevProps)).toBeTruthy();
35+
});
36+
37+
test('should return false if options are equal', () => {
38+
const nextProps = { disabled: true, root: 1, rootMargin: 2, threshold: [0.25, 0.75, 0.5] };
39+
const prevProps = { ...nextProps };
40+
41+
expect(comparerFn(nextProps, prevProps)).toBeFalsy();
42+
});
43+
44+
test('should return true if options are different', () => {
45+
const nextProps = { disabled: true, root: 1, rootMargin: 2, threshold: [0.25, 0.75, 0.5] };
46+
const prevProps = { ...nextProps, threshold: 1 };
47+
48+
expect(comparerFn(nextProps, prevProps)).toBeTruthy();
49+
});
50+
});

0 commit comments

Comments
 (0)