Errors in throttle function after component is unmounted
AleCaste opened this issue · 0 comments
Hello!
We are using this <ListView> component on some of our app screens.
When we enter screen A, the <ListView> component is mounted. All ok.
When we exit screen A, we unmount the whole screen, along with the <ListView> component.
The problem is that sometimes the throttle function used by <ListView> is still active (since it is an async function) and it calls its fn but now the <ListView> is already UNMOUNTED so an error like this is raised:
Uncaught TypeError: Cannot read property 'offsetHeight' of null
at ScrollView.getMetrics (ScrollView.js?b514:228)
at handleScroll (ScrollView.js?b514:83)
at eval (util.js?c94d:32)
Basically the error chain is the following:
// util.js
export function throttle(fn, delay) {
var delayFlag = true;
var firstInvoke = true;
// console.log('exec once');
return function _throttle(e) {
if (delayFlag) {
delayFlag = false;
setTimeout(function () {
delayFlag = true;
// console.log('exec delay time');
ERROR fn(e); --> ERROR HERE! (the throttled fn is called when the list component has been unmounted...)
// ScrollView.js
value: function componentDidMount() {
var _this3 = this;
var handleScroll = function handleScroll(e) {
ERROR return _this3.props.onScroll && _this3.props.onScroll(e, _this3.getMetrics()); --> ERROR HERE! (the onScroll function is called consequently, which involves a call to getMetrics)
// ScrollView.js
this.getMetrics = function () {
var isVertical = !_this5.props.horizontal;
if (_this5.props.useBodyScroll) {
// In chrome61 `document.body.scrollTop` is invalid,
// and add new `document.scrollingElement`(chrome61, iOS support).
// In old-android-browser and iOS `document.documentElement.scrollTop` is invalid.
var scrollNode = document.scrollingElement ? document.scrollingElement : document.body;
return {
visibleLength: window[isVertical ? 'innerHeight' : 'innerWidth'],
contentLength: _this5.ScrollViewRef[isVertical ? 'scrollHeight' : 'scrollWidth'],
offset: scrollNode[isVertical ? 'scrollTop' : 'scrollLeft']
};
}
return {
ERROR visibleLength: _this5.ScrollViewRef[isVertical ? 'offsetHeight' : 'offsetWidth'], --> ERROR HERE! (getMetrics fails since the component is unmounted!!!! and thus _this5.ScrollViewRef is NULL at this stage!!)
Basically the getMetrics
function fails since the component is unmounted and thus _this5.ScrollViewRef is NULL at this stage.
Obviously the above error does not happen all the time but is VERY common.
My recommendation is that:
- Use a throttle function like https://github.com/lodash/lodash/tree/4.1.1-npm-packages/lodash.throttle (https://www.npmjs.com/package/lodash.throttle) which has a cancel method built in. So inside the
componentWillUnmount
you call this cancel method of the throttle function. To me this is the recommended solution. - You add a condition inside the handleScroll function to check if the component is still mounted:
key: 'componentDidMount',
value: function componentDidMount() {
var _this3 = this;
_this3.mounted = true;
var handleScroll = function handleScroll(e) {
return _this3.mounted && _this3.props.onScroll && _this3.props.onScroll(e, _this3.getMetrics());
};
...
See the _this3.mounted
set and check. Obviously on the componentWillUnmount
part you should set that property to false:
key: 'componentWillUnmount',
value: function componentWillUnmount() {
if (this.props.useBodyScroll) {
window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.onLayout);
} else {
this.ScrollViewRef.removeEventListener('scroll', this.handleScroll);
}
this.mounted = false;
}
- Similarly we have to do something similar on 'componentDidUpdate':
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps) {
var _this2 = this;
// handle componentWillUpdate accordingly
if ((this.props.dataSource !== prevProps.dataSource || this.props.initialListSize !== prevProps.initialListSize) && this.handleScroll) {
setTimeout(function () {
if (_this2.props.useBodyScroll) {
window.addEventListener('scroll', _this2.handleScroll);
} else {
_this2.mounted && _this2.ScrollViewRef && _this2.ScrollViewRef.addEventListener('scroll', _this2.handleScroll);
}
}, 0);
}
}
...
... note the _this2.mounted
condition