Skip to content

Commit 4d9f594

Browse files
authored
Merge pull request #172 from joinhandshake/skovy/safely-navigate-null-scroll-component
Safely navigate a null scroll component (offsetHeight of null)
2 parents 0b1a5b4 + ac51e07 commit 4d9f594

File tree

3 files changed

+61
-10
lines changed

3 files changed

+61
-10
lines changed

dist/InfiniteScroll.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ var InfiniteScroll = (function(_Component) {
166166
{
167167
key: 'getParentElement',
168168
value: function getParentElement(el) {
169-
return el.parentNode;
169+
return el && el.parentNode;
170170
},
171171
},
172172
{
@@ -241,9 +241,7 @@ var InfiniteScroll = (function(_Component) {
241241
if (this.props.isReverse) {
242242
offset = scrollTop;
243243
} else {
244-
offset =
245-
this.calculateTopPosition(el) +
246-
(el.offsetHeight - scrollTop - window.innerHeight);
244+
offset = this.calculateOffset(el, scrollTop);
247245
}
248246
} else if (this.props.isReverse) {
249247
offset = parentNode.scrollTop;
@@ -253,7 +251,11 @@ var InfiniteScroll = (function(_Component) {
253251
}
254252

255253
// Here we make sure the element is visible as well as checking the offset
256-
if (offset < Number(this.props.threshold) && el.offsetParent !== null) {
254+
if (
255+
offset < Number(this.props.threshold) &&
256+
el &&
257+
el.offsetParent !== null
258+
) {
257259
this.detachScrollListener();
258260
// Call loadMore after detachScrollListener to allow for non-async loadMore functions
259261
if (typeof this.props.loadMore === 'function') {
@@ -262,6 +264,19 @@ var InfiniteScroll = (function(_Component) {
262264
}
263265
},
264266
},
267+
{
268+
key: 'calculateOffset',
269+
value: function calculateOffset(el, scrollTop) {
270+
if (!el) {
271+
return 0;
272+
}
273+
274+
return (
275+
this.calculateTopPosition(el) +
276+
(el.offsetHeight - scrollTop - window.innerHeight)
277+
);
278+
},
279+
},
265280
{
266281
key: 'calculateTopPosition',
267282
value: function calculateTopPosition(el) {

src/InfiniteScroll.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export default class InfiniteScroll extends Component {
8787
}
8888

8989
getParentElement(el) {
90-
return el.parentNode;
90+
return el && el.parentNode;
9191
}
9292

9393
filterProps(props) {
@@ -149,9 +149,7 @@ export default class InfiniteScroll extends Component {
149149
if (this.props.isReverse) {
150150
offset = scrollTop;
151151
} else {
152-
offset =
153-
this.calculateTopPosition(el) +
154-
(el.offsetHeight - scrollTop - window.innerHeight);
152+
offset = this.calculateOffset(el, scrollTop);
155153
}
156154
} else if (this.props.isReverse) {
157155
offset = parentNode.scrollTop;
@@ -160,7 +158,10 @@ export default class InfiniteScroll extends Component {
160158
}
161159

162160
// Here we make sure the element is visible as well as checking the offset
163-
if (offset < Number(this.props.threshold) && el.offsetParent !== null) {
161+
if (
162+
offset < Number(this.props.threshold) &&
163+
(el && el.offsetParent !== null)
164+
) {
164165
this.detachScrollListener();
165166
// Call loadMore after detachScrollListener to allow for non-async loadMore functions
166167
if (typeof this.props.loadMore === 'function') {
@@ -169,6 +170,17 @@ export default class InfiniteScroll extends Component {
169170
}
170171
}
171172

173+
calculateOffset(el, scrollTop) {
174+
if (!el) {
175+
return 0;
176+
}
177+
178+
return (
179+
this.calculateTopPosition(el) +
180+
(el.offsetHeight - scrollTop - window.innerHeight)
181+
);
182+
}
183+
172184
calculateTopPosition(el) {
173185
if (!el) {
174186
return 0;

test/infiniteScroll_test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,28 @@ describe('InfiniteScroll component', () => {
7575
InfiniteScroll.prototype.attachScrollListener.restore();
7676
InfiniteScroll.prototype.scrollListener.restore();
7777
});
78+
79+
it('should handle when the scrollElement is removed from the DOM', () => {
80+
const loadMore = stub();
81+
82+
const wrapper = mount(
83+
<div>
84+
<InfiniteScroll pageStart={0} loadMore={loadMore} hasMore={false}>
85+
<div className="child-component">Child Text</div>
86+
</InfiniteScroll>
87+
</div>,
88+
);
89+
90+
const component = wrapper.find(InfiniteScroll);
91+
92+
// The component has now mounted, but the scrollComponent is null
93+
component.instance().scrollComponent = null;
94+
95+
// Invoke the scroll listener which depends on the scrollComponent to
96+
// verify it executes properly, and safely navigates when the
97+
// scrollComponent is null.
98+
component.instance().scrollListener();
99+
100+
expect(wrapper.text()).to.contain('Child Text');
101+
});
78102
});

0 commit comments

Comments
 (0)