All files / src/containers/LoadingOverlayHandler index.jsx

0% Statements 0/101
0% Branches 0/57
0% Functions 0/23
0% Lines 0/52
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104                                                                                                                                                                                                               
import styles from './style.postcss';
 
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import is from 'is_js';
 
import connect from 'domain/connect';
import ApiCall from 'containers/ApiCalls';
import Config from 'domain/Config';
import testClass from 'domain/testClass';
import PageLoadingSpinner from 'components/PageLoadingSpinner';
 
const HTTP_METHOD_TRIGGERS = 'GET';
const REQUEST_DURATION_THRESHOLD_MS = 100;
 
// config feature flag
const IS_ACTIVE = !! Config.get('showLoadingOverlayForApiGets');
 
class LoadingOverlayHandler extends PureComponent {
  constructor() {
    super();
    this.state = { isThrobberVisible: false };
  }
 
  componentDidMount() {
    this._updateState();
  }
 
  componentWillReceiveProps(nextProps) {
    this._updateState(nextProps);
  }
 
  _updateState(props = this.props) {
    const { isAnyApiCallLoadingBeyondThreshold, isAnyApiCallLoading } = props;
    if (! IS_ACTIVE) return; // CONFIG flag check
    if (isAnyApiCallLoadingBeyondThreshold) {
      this.setState({ isThrobberVisible: true });
    } else if (isAnyApiCallLoading) {
      clearTimeout(this.timer);
      this.timer = setTimeout(() => {
        const { loadingApiCalls } = this.props;
        if (! getIsAnyApiCallLoadingBeyondThreshold(loadingApiCalls)) return;
        this.setState({ isThrobberVisible: true });
      }, REQUEST_DURATION_THRESHOLD_MS);
    } else if (! isAnyApiCallLoading) {
      clearTimeout(this.timer);
      this.setState({ isThrobberVisible: false });
    }
  }
 
  render() {
    const { children } = this.props;
    const classes = classnames({
      [testClass('is-any-api-call-loading')]: this.props.isAnyApiCallLoading,
    });
    return <div className={styles.LoadingOverlayHandler}>
      <PageLoadingSpinner className={classes}
          isHidden={! this.state.isThrobberVisible} />
      {children}
    </div>;
  }
}
 
LoadingOverlayHandler.propTypes = {
  loadingApiCalls: PropTypes.shape({
    filter: PropTypes.func,
  }),
  isAnyApiCallLoading: PropTypes.bool.isRequired,
  isAnyApiCallLoadingBeyondThreshold: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
};
 
function mapStateToProps(state) {
  const loadingApiCalls = getLoadingApiCalls(state);
  const isAnyApiCallLoadingBeyondThreshold =
      Boolean(loadingApiCalls && getIsAnyApiCallLoadingBeyondThreshold(loadingApiCalls));
  return {
    loadingApiCalls,
    isAnyApiCallLoading: !! loadingApiCalls,
    isAnyApiCallLoadingBeyondThreshold,
  };
}
 
function getLoadingApiCalls(state) {
  const loadingApiCalls = state.get('apiCalls')
    .filter((call, key) => key.startsWith(HTTP_METHOD_TRIGGERS))
    .filter((call) => ApiCall.State.isLoading(call))
    .filter((call) => ! call.disableDefault);
  if (is.object(loadingApiCalls) && loadingApiCalls.size) {
    return loadingApiCalls;
  }
  return null;
}
 
function getIsAnyApiCallLoadingBeyondThreshold(apiCalls) {
  if (! apiCalls) return false;
  return !! apiCalls.filter((call) =>
      new Date().getTime() - call.timestamp.getTime() >= REQUEST_DURATION_THRESHOLD_MS)
    .find(() => true);
}
 
export default connect(mapStateToProps)(LoadingOverlayHandler);