123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- import React from 'react';
- import styled from '@emotion/styled';
- import * as Sentry from '@sentry/react';
- import LoadingError from 'app/components/loadingError';
- import LoadingIndicator from 'app/components/loadingIndicator';
- import {t} from 'app/locale';
- import {isWebpackChunkLoadingError} from 'app/utils';
- import retryableImport from 'app/utils/retryableImport';
- type PromisedImport<C> = Promise<{default: C}>;
- type Component = React.ComponentType<any>;
- type Props<C extends Component> = Omit<
- React.ComponentProps<C>,
- 'hideBusy' | 'hideError' | 'component' | 'route'
- > & {
- hideBusy?: boolean;
- hideError?: boolean;
- /**
- * Function that returns a promise of a React.Component
- */
- component?: () => PromisedImport<C>;
- /**
- * Also accepts a route object from react-router that has a `componentPromise` property
- */
- route?: {componentPromise: () => PromisedImport<C>};
- };
- type State<C extends Component> = {
- Component: C | null;
- error: any | null;
- };
- class LazyLoad<C extends Component> extends React.Component<Props<C>, State<C>> {
- state: State<C> = {
- Component: null,
- error: null,
- };
- componentDidMount() {
- this.fetchComponent();
- }
- UNSAFE_componentWillReceiveProps(nextProps: Props<C>) {
- // No need to refetch when component does not change
- if (nextProps.component && nextProps.component === this.props.component) {
- return;
- }
- // This is to handle the following case:
- // <Route path="a/">
- // <Route path="b/" component={LazyLoad} componentPromise={...} />
- // <Route path="c/" component={LazyLoad} componentPromise={...} />
- // </Route>
- //
- // `LazyLoad` will get not fully remount when we switch between `b` and `c`,
- // instead will just re-render. Refetch if route paths are different
- if (nextProps.route && nextProps.route === this.props.route) {
- return;
- }
- // If `this.fetchComponent` is not in callback,
- // then there's no guarantee that new Component will be rendered
- this.setState(
- {
- Component: null,
- },
- this.fetchComponent
- );
- }
- componentDidCatch(error: any) {
- Sentry.captureException(error);
- this.handleError(error);
- }
- get componentGetter() {
- return this.props.component ?? this.props.route?.componentPromise;
- }
- handleFetchError = (error: any) => {
- Sentry.withScope(scope => {
- if (isWebpackChunkLoadingError(error)) {
- scope.setFingerprint(['webpack', 'error loading chunk']);
- }
- Sentry.captureException(error);
- });
- this.handleError(error);
- };
- handleError = (error: any) => {
- // eslint-disable-next-line no-console
- console.error(error);
- this.setState({error});
- };
- fetchComponent = async () => {
- const getComponent = this.componentGetter;
- if (getComponent === undefined) {
- return;
- }
- try {
- this.setState({Component: await retryableImport(getComponent)});
- } catch (err) {
- this.handleFetchError(err);
- }
- };
- fetchRetry = () => {
- this.setState({error: null}, this.fetchComponent);
- };
- render() {
- const {Component, error} = this.state;
- const {hideBusy, hideError, component: _component, ...otherProps} = this.props;
- if (error && !hideError) {
- return (
- <LoadingErrorContainer>
- <LoadingError
- onRetry={this.fetchRetry}
- message={t('There was an error loading a component.')}
- />
- </LoadingErrorContainer>
- );
- }
- if (!Component && !hideBusy) {
- return (
- <LoadingContainer>
- <LoadingIndicator />
- </LoadingContainer>
- );
- }
- if (Component === null) {
- return null;
- }
- return <Component {...(otherProps as React.ComponentProps<C>)} />;
- }
- }
- const LoadingContainer = styled('div')`
- display: flex;
- flex: 1;
- align-items: center;
- `;
- const LoadingErrorContainer = styled('div')`
- flex: 1;
- `;
- export default LazyLoad;
|