DEV Community

Composite
Composite

Posted on • Edited on

11 1

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!

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay