(2024-05-30: Added working example)
What the heck does that mean?
Today I'm going to break a React feature that I've been using for a while now.
The React.lazy
function is mainly for lazy importing of external components, and in general, the official documentation will tell you to write it like this:
export const MyExternalComponent = React.lazy(() =>
import('./path/to/my/component')
)
This is a popular optimization recommendation that wraps a <Suspense>
component around a component declared with the export default
statement in an external component file and imports it on the first call to save resources.
Right. It must be the component that you declared with the export default
statement. not a named export.
As you'll see when you write a dynamic import
statement, if you want to lazy load something that you've declared other than default export
, you have to return an object with the default
property.
export const MyExternalComponent = React.lazy(() =>
import('./path/to/my/component')
.then(({ OtherComponent }) => ({ default: OtherComponent }))
)
Or you can install react-lazly
package instead to make it more flexible.
From this feature, I came up with a brilliant idea.
export const IntrovertComponent = React.lazy(() =>
fetch('/path/to/api')
.then(res => res.json())
.then(json => {
const MyComponent = () => {
return <pre>{JSON.stringify(json, null, 2)}</pre>
}
return { default: MyComponent }
})
You might be wondering if the above code will work, right?
The short answer is yes. It will work.
Let's see in action.
Why?
React doesn't care if there's an import
statement in the function body or not. We're not necessarily using the Webpack bundler, so it's a pain for them to analyze the bundler's code output.
Instead, the value is in the ability to defer the import
statement.
Anyway, the argument to the lazy
function accepts a function that returns a Promise
instance, so it doesn't have to be an import
statement, just a Promise
object.
It'll useful when:
- Lazly loads 3rd party quite complex libraries before initializing your compoenent
- Lazly loads API and applies to your component(but once)
- Lazly loads your component that avoids SSR for some external reasons
Instead, there is one thing to keep in mind.
This behavior will be out of the purpose of the lazy
function.
Its original purpose is to optimize component resources with import
, but since you declared this code inline, you'll be far from optimizing resources.
I hope that in the upcoming React 19, do not implement this nonsense and create a normal component with a use
hook function.
The dynamic
provided by Next.js can be implemented in the same way. In particular, since the initialization function of the Plotly
library I was using returns a Promise
, I implemented the following to create a pure client component away from SSR. See also my original post
export const Plotly = dynamic(
() =>
import('plotly.js/dist/plotly.js').then(({ newPlot, purge }) => {
const Plotly = forwardRef(({ id, className, data, layout, config }, ref) => {
const originId = useId();
const realId = id || originId;
const originRef = useRef(null);
const [handle, setHandle] = useState(undefined);
useEffect(() => {
let instance;
originRef.current &&
newPlot(originRef.current!, data, layout, config).then((ref) => setHandle((instance = ref)));
return () => {
instance && purge(instance);
};
}, [data]);
useImperativeHandle(
ref,
() => (handle ?? originRef.current ?? document.createElement('div')),
[handle]
);
return <div id={realId} ref={originRef} className={className}></div>;
});
Plotly.displayName = 'Plotly';
return Plotly;
}),
{ ssr: false }
);
I named this component an introvert component.
Funny, huh?
Happy React'ing!
Top comments (0)