DEV Community

Composite
Composite

Posted on • Edited on

Make a React introvert Component (What?🤨)

(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')
)
Enter fullscreen mode Exit fullscreen mode

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 }))
)
Enter fullscreen mode Exit fullscreen mode

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 }
  })
Enter fullscreen mode Exit fullscreen mode

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 }
);
Enter fullscreen mode Exit fullscreen mode

I named this component an introvert component.
Funny, huh?

Happy React'ing!

Top comments (0)