Make a React introvert Component (What?🤨)
Tags: javascript, webdev, react, nextjs
Published: 2024-05-29 10:28:11

*(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:

```jsx
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.

```jsx
export const MyExternalComponent = React.lazy(() =>
  import('./path/to/my/component')
  .then(({ OtherComponent }) => ({ default: OtherComponent }))
)
```

Or you can install [`react-lazly`](https://www.npmjs.com/package/react-lazily) package instead to make it more flexible.

From this feature, I came up with a brilliant idea.

```jsx
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.

{% embed https://stackblitz.com/edit/vitejs-vite-gadhoh?embed=1&file=src%2FApp.jsx&hideExplorer=1 %}

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](https://dev.to/composite/how-to-integrate-plotlyjs-on-nextjs-14-with-app-router-1loj)

```jsx
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!