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.

106 lines
2.4 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. const emptyComponent = () => null;
  4. const noop = () => { };
  5. class Bundle extends React.PureComponent {
  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 = new Map
  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. const cachedMod = Bundle.cache.get(fetchComponent);
  45. if (fetchComponent === undefined) {
  46. this.setState({ mod: null });
  47. return Promise.resolve();
  48. }
  49. onFetch();
  50. if (cachedMod) {
  51. this.setState({ mod: cachedMod.default });
  52. onFetchSuccess();
  53. return Promise.resolve();
  54. }
  55. this.setState({ mod: undefined });
  56. if (renderDelay !== 0) {
  57. this.timestamp = new Date();
  58. this.timeout = setTimeout(() => this.setState({ forceRender: true }), renderDelay);
  59. }
  60. return fetchComponent()
  61. .then((mod) => {
  62. Bundle.cache.set(fetchComponent, mod);
  63. this.setState({ mod: mod.default });
  64. onFetchSuccess();
  65. })
  66. .catch((error) => {
  67. this.setState({ mod: null });
  68. onFetchFail(error);
  69. });
  70. }
  71. render() {
  72. const { loading: Loading, error: Error, children, renderDelay } = this.props;
  73. const { mod, forceRender } = this.state;
  74. const elapsed = this.timestamp ? (new Date() - this.timestamp) : renderDelay;
  75. if (mod === undefined) {
  76. return (elapsed >= renderDelay || forceRender) ? <Loading /> : null;
  77. }
  78. if (mod === null) {
  79. return <Error onRetry={this.load} />;
  80. }
  81. return children(mod);
  82. }
  83. }
  84. export default Bundle;