Delete Bundle component, use Suspense and React.lazy

This commit is contained in:
Alex Gleason
2023-10-07 17:14:45 -05:00
parent 1b213452b7
commit b02c39da2d
35 changed files with 275 additions and 578 deletions

View File

@@ -1,23 +0,0 @@
import React, { Suspense } from 'react';
export interface IBundle<T extends React.ComponentType<any>> {
fetchComponent: React.LazyExoticComponent<T>;
loading?: React.ComponentType;
error?: React.ComponentType<{ onRetry: (props?: IBundle<T>) => void }>;
children: (component: React.LazyExoticComponent<T>) => React.ReactNode;
renderDelay?: number;
onFetch?: () => void;
onFetchSuccess?: () => void;
onFetchFail?: (error: any) => void;
}
/** Fetches and renders an async component. */
function Bundle<T extends React.ComponentType<any>>({ fetchComponent, loading: Loading, children }: IBundle<T>) {
return (
<Suspense fallback={Loading ? <Loading /> : null}>
{children(fetchComponent)}
</Suspense>
);
}
export default Bundle;

View File

@@ -2,8 +2,6 @@ import React from 'react';
import { Spinner } from 'soapbox/components/ui';
// Keep the markup in sync with <BundleModalError />
// (make sure they have the same dimensions)
const ModalLoading = () => (
<div className='modal-root__modal error-modal'>
<div className='error-modal__body'>

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { Suspense } from 'react';
import Base from 'soapbox/components/modal-root';
import {
@@ -38,8 +38,6 @@ import {
VideoModal,
} from 'soapbox/features/ui/util/async-components';
import BundleContainer from '../containers/bundle-container';
import ModalLoading from './modal-loading';
/* eslint sort-keys: "error" */
@@ -102,7 +100,7 @@ export default class ModalRoot extends React.PureComponent<IModalRoot> {
}
}
renderLoading = (modalId: string) => () => {
renderLoading = (modalId: string) => {
return !['MEDIA', 'VIDEO', 'BOOST', 'CONFIRM', 'ACTIONS'].includes(modalId) ? <ModalLoading /> : null;
};
@@ -113,14 +111,14 @@ export default class ModalRoot extends React.PureComponent<IModalRoot> {
render() {
const { type, props } = this.props;
const visible = !!type;
const Component = type ? MODAL_COMPONENTS[type] : null;
return (
<Base onClose={this.onClickClose} type={type}>
{visible && (
<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} renderDelay={200}>
{(SpecificComponent) => <SpecificComponent {...props} onClose={this.onClickClose} />}
</BundleContainer>
{(Component && !!type) && (
<Suspense fallback={this.renderLoading(type)}>
<Component {...props} onClose={this.onClickClose} />
</Suspense>
)}
</Base>
);

View File

@@ -24,12 +24,13 @@ import { checkEventComposeContent } from 'soapbox/components/modal-root';
import { Button, Form, FormGroup, HStack, Icon, IconButton, Input, Modal, Spinner, Stack, Tabs, Text, Toggle } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container';
import { isCurrentOrFutureDate } from 'soapbox/features/compose/components/schedule-form';
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
import { ComposeEditor, DatePicker } from 'soapbox/features/ui/util/async-components';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import UploadButton from './upload-button';
import type { LexicalEditor } from 'lexical';
const messages = defineMessages({
eventNamePlaceholder: { id: 'compose_event.fields.name_placeholder', defaultMessage: 'Name' },
eventDescriptionPlaceholder: { id: 'compose_event.fields.description_placeholder', defaultMessage: 'Description' },
@@ -94,7 +95,7 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const editorStateRef = useRef<string>(null);
const editorRef = useRef<LexicalEditor>(null);
const [tab, setTab] = useState<'edit' | 'pending'>('edit');
@@ -167,7 +168,7 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
};
const handleSubmit = () => {
dispatch(changeEditEventDescription(editorStateRef.current!));
dispatch(changeEditEventDescription(editorRef.current!));
dispatch(submitEvent());
};
@@ -235,18 +236,14 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
<FormGroup
labelText={<FormattedMessage id='compose_event.fields.description_label' defaultMessage='Event description' />}
>
<BundleContainer fetchComponent={ComposeEditor}>
{(Component: any) => (
<Component
ref={editorStateRef}
className='block w-full rounded-md border border-gray-400 bg-white px-3 py-2 text-base text-gray-900 ring-1 placeholder:text-gray-600 focus-within:border-primary-500 focus-within:ring-primary-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500 sm:text-sm'
placeholderClassName='pt-2'
composeId='compose-event-modal'
placeholder={intl.formatMessage(messages.eventDescriptionPlaceholder)}
handleSubmit={handleSubmit}
/>
)}
</BundleContainer>
<ComposeEditor
ref={editorRef}
className='block w-full rounded-md border border-gray-400 bg-white px-3 py-2 text-base text-gray-900 ring-1 placeholder:text-gray-600 focus-within:border-primary-500 focus-within:ring-primary-500 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-100 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus-within:border-primary-500 dark:focus-within:ring-primary-500 sm:text-sm'
placeholderClassName='pt-2'
composeId='compose-event-modal'
placeholder={intl.formatMessage(messages.eventDescriptionPlaceholder)}
handleSubmit={handleSubmit}
/>
</FormGroup>
<FormGroup
labelText={<FormattedMessage id='compose_event.fields.location_label' defaultMessage='Event location' />}
@@ -260,18 +257,16 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
<FormGroup
labelText={<FormattedMessage id='compose_event.fields.start_time_label' defaultMessage='Event start date' />}
>
<BundleContainer fetchComponent={DatePicker}>
{Component => (<Component
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
wrapperClassName='react-datepicker-wrapper'
placeholderText={intl.formatMessage(messages.eventStartTimePlaceholder)}
filterDate={isCurrentOrFutureDate}
selected={startTime}
onChange={onChangeStartTime}
/>)}
</BundleContainer>
<DatePicker
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
wrapperClassName='react-datepicker-wrapper'
placeholderText={intl.formatMessage(messages.eventStartTimePlaceholder)}
filterDate={isCurrentOrFutureDate}
selected={startTime}
onChange={onChangeStartTime}
/>
</FormGroup>
<HStack alignItems='center' space={2}>
<Toggle
@@ -286,18 +281,16 @@ const ComposeEventModal: React.FC<IComposeEventModal> = ({ onClose }) => {
<FormGroup
labelText={<FormattedMessage id='compose_event.fields.end_time_label' defaultMessage='Event end date' />}
>
<BundleContainer fetchComponent={DatePicker}>
{Component => (<Component
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
wrapperClassName='react-datepicker-wrapper'
placeholderText={intl.formatMessage(messages.eventEndTimePlaceholder)}
filterDate={isCurrentOrFutureDate}
selected={endTime}
onChange={onChangeEndTime}
/>)}
</BundleContainer>
<DatePicker
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
wrapperClassName='react-datepicker-wrapper'
placeholderText={intl.formatMessage(messages.eventEndTimePlaceholder)}
filterDate={isCurrentOrFutureDate}
selected={endTime}
onChange={onChangeEndTime}
/>
</FormGroup>
)}
{!id && (

View File

@@ -4,7 +4,6 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import { changeAnnouncementAllDay, changeAnnouncementContent, changeAnnouncementEndTime, changeAnnouncementStartTime, handleCreateAnnouncement } from 'soapbox/actions/admin';
import { closeModal } from 'soapbox/actions/modals';
import { Form, FormGroup, HStack, Modal, Stack, Text, Textarea, Toggle } from 'soapbox/components/ui';
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
import { DatePicker } from 'soapbox/features/ui/util/async-components';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
@@ -67,34 +66,30 @@ const EditAnnouncementModal: React.FC<IEditAnnouncementModal> = ({ onClose }) =>
<FormGroup
labelText={<FormattedMessage id='admin.edit_announcement.fields.start_time_label' defaultMessage='Start date' />}
>
<BundleContainer fetchComponent={DatePicker}>
{Component => (<Component
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
wrapperClassName='react-datepicker-wrapper'
placeholderText={intl.formatMessage(messages.announcementStartTimePlaceholder)}
selected={startTime}
onChange={onChangeStartTime}
isClearable
/>)}
</BundleContainer>
<DatePicker
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
wrapperClassName='react-datepicker-wrapper'
placeholderText={intl.formatMessage(messages.announcementStartTimePlaceholder)}
selected={startTime}
onChange={onChangeStartTime}
isClearable
/>
</FormGroup>
<FormGroup
labelText={<FormattedMessage id='admin.edit_announcement.fields.end_time_label' defaultMessage='End date' />}
>
<BundleContainer fetchComponent={DatePicker}>
{Component => (<Component
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
wrapperClassName='react-datepicker-wrapper'
placeholderText={intl.formatMessage(messages.announcementEndTimePlaceholder)}
selected={endTime}
onChange={onChangeEndTime}
isClearable
/>)}
</BundleContainer>
<DatePicker
showTimeSelect
dateFormat='MMMM d, yyyy h:mm aa'
timeIntervals={15}
wrapperClassName='react-datepicker-wrapper'
placeholderText={intl.formatMessage(messages.announcementEndTimePlaceholder)}
selected={endTime}
onChange={onChangeEndTime}
isClearable
/>
</FormGroup>
<HStack alignItems='center' space={2}>
<Toggle

View File

@@ -5,7 +5,6 @@ import { FormattedMessage } from 'react-intl';
import { fetchPinnedAccounts } from 'soapbox/actions/accounts';
import { Widget } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container';
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
import { WhoToFollowPanel } from 'soapbox/features/ui/util/async-components';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
@@ -26,9 +25,7 @@ const PinnedAccountsPanel: React.FC<IPinnedAccountsPanel> = ({ account, limit })
if (pinned.isEmpty()) {
return (
<BundleContainer fetchComponent={WhoToFollowPanel}>
{Component => <Component limit={limit} />}
</BundleContainer>
<WhoToFollowPanel limit={limit} />
);
}

View File

@@ -4,7 +4,6 @@ import { defineMessages, useIntl, FormatDateOptions } from 'react-intl';
import Markup from 'soapbox/components/markup';
import { HStack, Icon } from 'soapbox/components/ui';
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
import { CryptoAddress } from 'soapbox/features/ui/util/async-components';
import type { Account } from 'soapbox/schemas';
@@ -35,14 +34,10 @@ const ProfileField: React.FC<IProfileField> = ({ field }) => {
if (isTicker(field.name)) {
return (
<BundleContainer fetchComponent={CryptoAddress}>
{Component => (
<Component
ticker={getTicker(field.name).toLowerCase()}
address={field.value_plain}
/>
)}
</BundleContainer>
<CryptoAddress
ticker={getTicker(field.name).toLowerCase()}
address={field.value_plain}
/>
);
}

View File

@@ -1,3 +0,0 @@
import Bundle from '../components/bundle';
export default Bundle;

View File

@@ -41,7 +41,6 @@ import { isStandalone } from 'soapbox/utils/state';
import BackgroundShapes from './components/background-shapes';
import FloatingActionButton from './components/floating-action-button';
import Navbar from './components/navbar';
import BundleContainer from './containers/bundle-container';
import {
Status,
CommunityTimeline,
@@ -501,29 +500,18 @@ const UI: React.FC<IUI> = ({ children }) => {
)}
{me && (
<BundleContainer fetchComponent={SidebarMenu}>
{Component => <Component />}
</BundleContainer>
<SidebarMenu />
)}
{me && features.chats && (
<BundleContainer fetchComponent={ChatWidget}>
{Component => (
<div className='hidden xl:block'>
<Component />
</div>
)}
</BundleContainer>
<div className='hidden xl:block'>
<ChatWidget />
</div>
)}
<ThumbNavigation />
<BundleContainer fetchComponent={ProfileHoverCard}>
{Component => <Component />}
</BundleContainer>
<BundleContainer fetchComponent={StatusHoverCard}>
{Component => <Component />}
</BundleContainer>
<ProfileHoverCard />
<StatusHoverCard />
</div>
</div>
</GlobalHotkeys>

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { Suspense } from 'react';
import { Redirect, Route, useHistory, RouteProps, RouteComponentProps, match as MatchType } from 'react-router-dom';
import { Layout } from 'soapbox/components/ui';
@@ -7,7 +7,6 @@ import { useOwnAccount, useSettings } from 'soapbox/hooks';
import ColumnForbidden from '../components/column-forbidden';
import ColumnLoading from '../components/column-loading';
import ColumnsArea from '../components/columns-area';
import BundleContainer from '../containers/bundle-container';
type PageProps = {
params?: MatchType['params'];
@@ -28,7 +27,7 @@ interface IWrappedRoute extends RouteProps {
}
const WrappedRoute: React.FC<IWrappedRoute> = ({
component,
component: Component,
page: Page,
content,
componentParams = {},
@@ -47,32 +46,24 @@ const WrappedRoute: React.FC<IWrappedRoute> = ({
const renderComponent = ({ match }: RouteComponentProps) => {
if (Page) {
return (
<BundleContainer fetchComponent={component} loading={renderLoading}>
{Component =>
(
<Page params={match.params} layout={layout} {...componentParams}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</Page>
)
}
</BundleContainer>
<Suspense fallback={renderLoading()}>
<Page params={match.params} layout={layout} {...componentParams}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</Page>
</Suspense>
);
}
return (
<BundleContainer fetchComponent={component} loading={renderLoading}>
{Component =>
(
<ColumnsArea layout={layout}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</ColumnsArea>
)
}
</BundleContainer>
<Suspense fallback={renderLoading()}>
<ColumnsArea layout={layout}>
<Component params={match.params} {...componentParams}>
{content}
</Component>
</ColumnsArea>
</Suspense>
);
};