You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

102 lines
2.3 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. const emptyComponent = () => null;
  4. const noop = () => { };
  5. class Bundle extends React.Component {
  6. static propTypes = {
  7. fetchComponent: PropTypes.func.isRequired,
  8. loading: PropTypes.func,
  9. error: PropTypes.func,
  10. children: PropTypes.func.isRequired,
  11. renderDelay: PropTypes.number,
  12. onFetch: PropTypes.func,
  13. onFetchSuccess: PropTypes.func,
  14. onFetchFail: PropTypes.func,
  15. }
  16. static defaultProps = {
  17. loading: emptyComponent,
  18. error: emptyComponent,
  19. renderDelay: 0,
  20. onFetch: noop,
  21. onFetchSuccess: noop,
  22. onFetchFail: noop,
  23. }
  24. static cache = {}
  25. state = {
  26. mod: undefined,
  27. forceRender: false,
  28. }
  29. componentWillMount() {
  30. this.load(this.props);
  31. }
  32. componentWillReceiveProps(nextProps) {
  33. if (nextProps.fetchComponent !== this.props.fetchComponent) {
  34. this.load(nextProps);
  35. }
  36. }
  37. componentWillUnmount () {
  38. if (this.timeout) {
  39. clearTimeout(this.timeout);
  40. }
  41. }
  42. load = (props) => {
  43. const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
  44. onFetch();
  45. if (Bundle.cache[fetchComponent.name]) {
  46. const mod = Bundle.cache[fetchComponent.name];
  47. this.setState({ mod: mod.default });
  48. onFetchSuccess();
  49. return Promise.resolve();
  50. }
  51. this.setState({ mod: undefined });
  52. if (renderDelay !== 0) {
  53. this.timestamp = new Date();
  54. this.timeout = setTimeout(() => this.setState({ forceRender: true }), renderDelay);
  55. }
  56. return fetchComponent()
  57. .then((mod) => {
  58. Bundle.cache[fetchComponent.name] = mod;
  59. this.setState({ mod: mod.default });
  60. onFetchSuccess();
  61. })
  62. .catch((error) => {
  63. this.setState({ mod: null });
  64. onFetchFail(error);
  65. });
  66. }
  67. render() {
  68. const { loading: Loading, error: Error, children, renderDelay } = this.props;
  69. const { mod, forceRender } = this.state;
  70. const elapsed = this.timestamp ? (new Date() - this.timestamp) : renderDelay;
  71. if (mod === undefined) {
  72. return (elapsed >= renderDelay || forceRender) ? <Loading /> : null;
  73. }
  74. if (mod === null) {
  75. return <Error onRetry={this.load} />;
  76. }
  77. return children(mod);
  78. }
  79. }
  80. export default Bundle;