AlreadyLost
AlreadyLost

Reputation: 827

use of React.Lazy for services (non-component) type of code

Per documentation, React.Lazy needs to be wrapped in a <suspense>. Can I use React.Lazy to lazily load a service which exports a function?

    const actionsList = React.lazy(() => import('@pkg/actions-list-service'));

and the action-List-service exports a function in a .ts file. What does React recommended way for lazily loading non-component type of code? This is the error I get trying to lazily load the service code I got this error:

Type 'Promise<{ default: typeof import("D:/services/actions-list-service/dist/types"); useActionsList: () => (itemElement: HTMLElement | null | undefined) => readonly ActionsListItem[]; }>' is not assignable to type 'Promise<{ default: ComponentType<any>; }>'.

Upvotes: 5

Views: 3045

Answers (3)

D&#252;rrani
D&#252;rrani

Reputation: 1266

My solution for dynamically importing google-libphonenumber (a heavy library that must be lazily loaded)

phoneutil.ts

/** Dynamic import for code splitting/lazy loading. */
const googleLibphoneNumberModule = () => import('google-libphonenumber');

let phoneUtil: libphonenumber.PhoneNumberUtil;
const modulePromise = googleLibphoneNumberModule().then(({ default: { PhoneNumberUtil } }) => {
  phoneUtil = PhoneNumberUtil.getInstance();
});

export async function isPhoneNumberValid(phone: string) {
  try {
    await modulePromise;
    return phoneUtil.isValidNumber(phoneUtil.parseAndKeepRawInput(phone));
  } catch (error) {
    return false;
  }
}

Upvotes: 0

Christos Lytras
Christos Lytras

Reputation: 37327

If we want to trigger React Suspense loader, then we have to provide a component that will load the independent library.

For example I want to lazy load the html5-qrcode Html5Qrcode module that it's 314K (gzipped 93.5K).

We create a loader component that will have an onLoaded callback prop and it just returns the module like:

import { useEffect } from 'react';
import { Html5Qrcode } from 'html5-qrcode/esm/html5-qrcode';

export default function Html5QrcodeLoader({ onLoaded }) {
  useEffect(() => {
    onLoaded(Html5Qrcode);
  }, []);
  return null;
}

Then we import that component using React.lazy like:

const Html5QrcodeLoader = React.lazy(() =>
  import(
    '@components/Html5QrcodeLoader' /* webpackChunkName: "Html5QrcodeLoader" */
  )
);

And now we can use the loader to our component like this:

export default function QrCodeScanner() {
  const [Html5Qrcode, setHtml5Qrcode] = useState();

  useEffect(() => {
    if (!Html5Qrcode) {
      return;
    }

    // Use lazy loaded module Html5Qrcode
  }, [Html5Qrcode]);

  const handleHtml5QrcodeLoaded = module => {
    // Careful, always do it using a function,
    // else the module itself will be initialized!
    setHtml5Qrcode(() => module);
  };

  if (!Html5Qrcode) {
    // Lazy load it
    return <Html5QrcodeLoader onLoaded={handleHtml5QrcodeLoaded} />;
  }

  log('Html5Qrcode loaded');

  // Now render

  return (
    <div>
      <div>QrCodeScanner</div>
    </div>
  );
}

Upvotes: 2

qur2
qur2

Reputation: 480

If you check the source of React.lazy(), you'll see that it's geared towards component rendering, as you could guess. In order to lazy load something, you can just use async import:

// example code, to show possible solution

const moduleMap = {
  module1: () => import('./module1.js'),
  module2: () => import('./module2.js')
}

function doSomething(moduleName) {
  // You might call this one in useEffect() for example
  const module = moduleMap[moduleName]
  if (module) {
    module().then(({default: actualModule, member1}) => {
      // use the module members
    })
  }
}

That will allow lazy loading and possibly code splitting as well.

Upvotes: 3

Related Questions