I want to delay loading of heavy Web3 UI components. They should only be initialized when the user actually needs them. So I am using Suspense and React Loadable to achieve this:
import { State } from '/imports/api/redux/State';
import ErrorBoundary from '/imports/ui/containers/ErrorBoundary';
import loadable from '@loadable/component';
import React, { Suspense } from 'react';
import { useSelector } from 'react-redux';
const Web3PortalRoot = () => {
const { active } = useSelector((state: State) => state.web3);
const Web3PortalContent = false ? loadable({ // <<-- this line
loader: () => import('./Web3PortalContent')
}) : () => null;
console.log({ active });
return (
<ErrorBoundary>
{active && (
<Suspense fallback={<div>Loading Web3 UI...</div>}>
{/*<Web3PortalContent />*/}
</Suspense>
)}
</ErrorBoundary>
);
};
export default Web3PortalRoot;
The strange thing is: As soon as the line const Web3PortalContent = ... is included, the bundle visualizer’s total bundle size jumps from 3.81mb to 4.16mb, indicating that some additional libraries will be loaded. One of them is viem, used by the portal content. This happens even though the loadable is never executed (due to the false condition; originally I had the active flag controlling this).
Interestingly, the visualizer does not report all of the dependencies of the portal content, just some smaller dependencies (like viem), maybe because the bigger dependencies are incapsulated in additional loadables.
Is this behavior a bug in Meteor’s bundler or just the bundle visualizer?
I am not sure () => null can be a React component. You would need to return at least an empty span. If I am not wrong, any functional component in React has to return something of a JSX (html) kind.
The way you would use a conditional loadable is to condition this component at its parent level. If you render the component that contains a loadable component, that is being loaded from … wherever it is, the local browser cache or from the Node/Meteor server.
export defaul function AParentComponent = props => {
if (true) {
return <Web3PortalRoot /> // this will load <Web3PortalContent /> dynamically
} else { return <span /> }
}
The following can be written more efficient in a slightly different way:
const { active } = useSelector((state: State) => state.web3)
// I don't use TS so you can TS-ify it.
const active = useSelector(({ web3 }) => web3.active)
Explanation: I do not know what is in your state, but if that is the whole Redux state, that is not ok.
You would normall have an object rootReducer with multiple reducers (eg. people, activities, events etc).
When you import a prop to a component, you import from its direct (sub-) reducer and not from the top reducer (the rootReducer) - but that is also technically correct.
Since I don’t know if state for you is one of the reducers or the rootReducer, I have nothing else to comment other than that you should not do name import from a reducer. There are 2 considerations here:
You could do const { active } = useSelector((state: State) => state.web3) || {} // to fall back in case web3 cannot be destructured. But …
If you do a name import, you import the whole reducer and the whole reducer will be a prop in your component which means that any updates to the reducer which have nothing to do with this component, will trigger a re-render.