diff --git a/src/features/emoji/components/emoji-picker-dropdown.tsx b/src/features/emoji/components/emoji-picker-dropdown.tsx index 379ae9c5b9..8917272b38 100644 --- a/src/features/emoji/components/emoji-picker-dropdown.tsx +++ b/src/features/emoji/components/emoji-picker-dropdown.tsx @@ -9,12 +9,10 @@ import { useAppDispatch, useAppSelector, useTheme } from 'soapbox/hooks'; import { RootState } from 'soapbox/store'; import { buildCustomEmojis } from '../../emoji'; -import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; +import { EmojiPicker } from '../../ui/util/async-components'; import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji'; -let EmojiPicker: any; // load asynchronously - export const messages = defineMessages({ emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, emoji_pick: { id: 'emoji_button.pick', defaultMessage: 'Pick an emojiā€¦' }, @@ -136,8 +134,6 @@ const EmojiPickerDropdown: React.FC = ({ const customEmojis = useAppSelector((state) => getCustomEmojis(state)); const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state)); - const [loading, setLoading] = useState(false); - const handlePick = (emoji: any) => { setVisible(false); @@ -210,18 +206,6 @@ const EmojiPickerDropdown: React.FC = ({ } else { document.body.style.overflow = ''; } - - if (!EmojiPicker) { - setLoading(true); - - EmojiPickerAsync().then(EmojiMart => { - EmojiPicker = EmojiMart.Picker; - - setLoading(false); - }).catch(() => { - setLoading(false); - }); - } }, [visible]); useEffect(() => () => { @@ -231,23 +215,21 @@ const EmojiPickerDropdown: React.FC = ({ return ( visible ? ( - {!loading && ( - - )} + ) : null ); diff --git a/src/features/ui/components/bundle.tsx b/src/features/ui/components/bundle.tsx index 58157901dc..dc84acf69c 100644 --- a/src/features/ui/components/bundle.tsx +++ b/src/features/ui/components/bundle.tsx @@ -1,114 +1,23 @@ -import React from 'react'; +import React, { Suspense } from 'react'; -const emptyComponent = () => null; -const noop = () => { }; - -export interface BundleProps { - fetchComponent: () => Promise; - loading: React.ComponentType; - error: React.ComponentType<{ onRetry: (props?: BundleProps) => void }>; - children: (mod: any) => React.ReactNode; +export interface IBundle> { + fetchComponent: React.LazyExoticComponent; + loading?: React.ComponentType; + error?: React.ComponentType<{ onRetry: (props?: IBundle) => void }>; + children: (component: React.LazyExoticComponent) => React.ReactNode; renderDelay?: number; - onFetch: () => void; - onFetchSuccess: () => void; - onFetchFail: (error: any) => void; -} - -interface BundleState { - mod: any; - forceRender: boolean; + onFetch?: () => void; + onFetchSuccess?: () => void; + onFetchFail?: (error: any) => void; } /** Fetches and renders an async component. */ -class Bundle extends React.PureComponent { - - timeout: NodeJS.Timeout | undefined; - timestamp: Date | undefined; - - static defaultProps = { - loading: emptyComponent, - error: emptyComponent, - renderDelay: 0, - onFetch: noop, - onFetchSuccess: noop, - onFetchFail: noop, - }; - - static cache = new Map; - - state = { - mod: undefined, - forceRender: false, - }; - - componentDidMount() { - this.load(this.props); - } - - UNSAFE_componentWillReceiveProps(nextProps: BundleProps) { - if (nextProps.fetchComponent !== this.props.fetchComponent) { - this.load(nextProps); - } - } - - componentWillUnmount() { - if (this.timeout) { - clearTimeout(this.timeout); - } - } - - load = (props?: BundleProps) => { - const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props; - const cachedMod = Bundle.cache.get(fetchComponent); - - if (fetchComponent === undefined) { - this.setState({ mod: null }); - return Promise.resolve(); - } - - onFetch(); - - if (cachedMod) { - this.setState({ mod: cachedMod.default }); - onFetchSuccess(); - return Promise.resolve(); - } - - this.setState({ mod: undefined }); - - if (renderDelay !== 0) { - this.timestamp = new Date(); - this.timeout = setTimeout(() => this.setState({ forceRender: true }), renderDelay); - } - - return fetchComponent() - .then((mod) => { - Bundle.cache.set(fetchComponent, mod); - this.setState({ mod: mod.default }); - onFetchSuccess(); - }) - .catch((error) => { - this.setState({ mod: null }); - onFetchFail(error); - }); - }; - - render() { - const { loading: Loading, error: Error, children, renderDelay } = this.props; - const { mod, forceRender } = this.state; - const elapsed = this.timestamp ? ((new Date()).getTime() - this.timestamp.getTime()) : renderDelay!; - - if (mod === undefined) { - return (elapsed >= renderDelay! || forceRender) ? : null; - } - - if (mod === null) { - return ; - } - - return children(mod); - } - +function Bundle>({ fetchComponent, loading: Loading, children }: IBundle) { + return ( + : null}> + {children(fetchComponent)} + + ); } export default Bundle; diff --git a/src/features/ui/containers/bundle-container.tsx b/src/features/ui/containers/bundle-container.tsx index 50a8b8629b..b94b1bb024 100644 --- a/src/features/ui/containers/bundle-container.tsx +++ b/src/features/ui/containers/bundle-container.tsx @@ -1,21 +1,3 @@ -import { connect } from 'react-redux'; - -import { fetchBundleRequest, fetchBundleSuccess, fetchBundleFail } from 'soapbox/actions/bundles'; - import Bundle from '../components/bundle'; -import type { AppDispatch } from 'soapbox/store'; - -const mapDispatchToProps = (dispatch: AppDispatch) => ({ - onFetch() { - dispatch(fetchBundleRequest()); - }, - onFetchSuccess() { - dispatch(fetchBundleSuccess()); - }, - onFetchFail(error: any) { - dispatch(fetchBundleFail(error)); - }, -}); - -export default connect(null, mapDispatchToProps)(Bundle); +export default Bundle; diff --git a/src/features/ui/index.tsx b/src/features/ui/index.tsx index 0e1f30a083..cbc8590b53 100644 --- a/src/features/ui/index.tsx +++ b/src/features/ui/index.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import React, { useEffect, useRef } from 'react'; +import React, { lazy, useEffect, useRef } from 'react'; import { Switch, useHistory, useLocation, Redirect } from 'react-router-dom'; import { fetchFollowRequests } from 'soapbox/actions/accounts'; @@ -354,7 +354,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => - new Promise((_resolve, reject) => reject())} content={children} /> + Promise.reject())} content={children} /> {hasCrypto && } diff --git a/src/features/ui/util/react-router-helpers.tsx b/src/features/ui/util/react-router-helpers.tsx index 911e3b4301..02a5fb1801 100644 --- a/src/features/ui/util/react-router-helpers.tsx +++ b/src/features/ui/util/react-router-helpers.tsx @@ -17,7 +17,7 @@ type PageProps = { }; interface IWrappedRoute extends RouteProps { - component: (...args: any[]) => any; + component: React.LazyExoticComponent; page?: React.ComponentType; content?: React.ReactNode; componentParams?: Record;