Advanced

Lazy

Content loaded through a dynamic import or any Promise; the loading flow, options, retries, cancellation and error reporting.

Lazy calls a function that produces a Promise, turns the result into a component and places it in a Frame. It is designed for code splitting (() => import('./X')), but it works with any Promise.

Usage

In the example, the folder chart is loaded lazily so that Chart.js is downloaded only when needed. The component is a named export, so mapResult picks it from the module:

<Lazy
  caller={() => import("../parts/FolderChart")}
  options={{
    mapResult: (m) => m.FolderChart,
    Loaderview: <p class="loading">Loading chart…</p>,
    Fallbackview: <p class="error">Couldn't load the chart.</p>,
    minDelayMs: 300,
    timeoutMs: 10000,
    retry: 3,
  }}
/>
const chart = Lazy({
  caller: () => import("../parts/FolderChart"),
  options: {
    mapResult: (m) => m.FolderChart,
    Loaderview: motifComponent("p", (p: ComponentBase) => {
      p.class.add("loading");
      p.setText("Loading chart…");
    }),
    Fallbackview: motifComponent("p", (p: ComponentBase) => {
      p.class.add("error");
      p.setText("Couldn't load the chart.");
    }),
    minDelayMs: 300,
    timeoutMs: 10000,
    retry: 3,
  },
});
<Lazy
  caller={() => import("../parts/FolderChart")}
  options={{
    mapResult: (m) => m.FolderChart,
    Loaderview: <p class="loading">Loading chart…</p>,
    Fallbackview: <p class="error">Couldn't load the chart.</p>,
    minDelayMs: 300,
    timeoutMs: 10000,
    retry: 3,
  }}
/>
const chart = Lazy({
  caller: () => import("../parts/FolderChart"),
  options: {
    mapResult: (m) => m.FolderChart,
    Loaderview: motifComponent("p", (p) => {
      p.class.add("loading");
      p.setText("Loading chart…");
    }),
    Fallbackview: motifComponent("p", (p) => {
      p.class.add("error");
      p.setText("Couldn't load the chart.");
    }),
    minDelayMs: 300,
    timeoutMs: 10000,
    retry: 3,
  },
});

Lazy is a function; a Lazy({ caller, options }) call returns the same Frame and can be placed with controls.add. frame.navigateLazy(caller, options) and frame.navigate(promise) use the same path.

The loading flow

  1. Placeholderview (or Loaderview if there is none) is shown right away.
  2. caller() is called right away (it does not wait for the component to enter the DOM). If timeoutMs is given, the load counts as failed with MJX110 when the time is up; if signal is given, abort() cuts the load off with MJX111.
  3. A successful result is turned into a component with mapResult. The default converter takes the module's default export (for the result of import()); if another export is needed, write mapResult: (m) => m.FolderChart as in the example. If the returned value is a class it is built with new, and if it is a function it is built by calling it; a ready instance is used as it is (no props are given). An Options API object or factory is turned into a component as well. A module without a default export, or some other object that is not a component, makes the load fail with MJX127 and moves on to step 5. null keeps the loading view, text is placed as a text node, and a promise or an array is placed as it resolves.
  4. If minDelayMs is given and the load finished sooner, the remaining time is waited out (this keeps the loading view from flashing on short loads). Then the content is placed in the Frame; Loaderview plays its leave transition and is disposed.
  5. On failure, onError(err) is called first. If there is a Fallbackview, it is shown and the error stays there (it is not reported). Otherwise the container is emptied and the error is reported as MJX126 with errorHandler.report — it is written to console.error and goes to the errorHandler.addListener listeners, with the original error in the cause field (see Error handling - Listening for reports). Having given onError does not remove the report.

For a cancelled (signal) load, onError is called once with MJX111; it makes no difference whether the cancellation arrives during an attempt or while waiting to retry. Fallbackview is not shown, the content does not change and no report is written. When the container is disposed, everything stops at the first moment of disposal: running timers and pending retries are cleared, onError is not called, no report is written, a load that finishes later is ignored and nothing is built.

Options (LazyOptions<T>)

Option Type Description
Loaderview ComponentBase The content shown while loading is in progress.
Placeholderview ComponentBase Takes precedence over Loaderview; when both are given, only this one is shown.
Fallbackview ComponentBase The content shown when the last attempt also fails.
minDelayMs number The minimum time that must pass before the content is placed.
timeoutMs number The upper limit of each attempt; MJX110 when exceeded.
signal AbortSignal Cancelling the load from outside; MJX111. Frame.navigate(promise) gives this itself.
mapResult (result) => component Turns the load result into a component; the default export by default.
onError (error) => void When the last attempt also fails, and on cancellation (MJX111), before Fallbackview; not called during disposal.
retry number \| LazyRetryOptions Retrying (below).
onRetry (attempt, error) => void Before every retry; attempt starts at 1.

Retrying — retry

<Lazy caller={caller} options={{ retry: 3 }} />                                              {/* count: 3, delayMs: 500, whenOnline: true */}
<Lazy caller={caller} options={{ retry: { count: 3, delayMs: 500, whenOnline: true } }} />   {/* explicit form (LazyRetryOptions) */}
Lazy({ caller, options: { retry: 3 } });                                              // count: 3, delayMs: 500, whenOnline: true
Lazy({ caller, options: { retry: { count: 3, delayMs: 500, whenOnline: true } } });   // explicit form (LazyRetryOptions)
<Lazy caller={caller} options={{ retry: 3 }} />                                              {/* count: 3, delayMs: 500, whenOnline: true */}
<Lazy caller={caller} options={{ retry: { count: 3, delayMs: 500, whenOnline: true } }} />   {/* explicit form (LazyRetryOptions) */}
Lazy({ caller, options: { retry: 3 } });                                              // count: 3, delayMs: 500, whenOnline: true
Lazy({ caller, options: { retry: { count: 3, delayMs: 500, whenOnline: true } } });   // explicit form (LazyRetryOptions)

In route pages

control: () => import('./pages/X') in a route definition and fallbacks.notFound/error use their own lazy loading path; the Lazy options (loading view, retries) do not apply there. To lazily load a heavy part inside a page, use Lazy (see The Route Table - Lazy loading).

Next step

To show content in another slot, go on to Transport and TransportTo. For the framework's error reporting model, see Error Handling; for the container's timing rules, Frame.