When one throws

There is no boundary component to wrap things in, because a throw does not unmount anything. The reconciler catches it around the component that threw, renders something in its place, and leaves the rest of the tree standing:

above
Boom: kaboom
below

The failure also reaches app.errors() with its stack, and the application stays interactive - resizing, input and every sibling keep working. This is the difference from React, where an uncaught throw takes the tree down and a boundary is what stops it. Here containment is the default and a fallback only changes what is shown.

Declare one on the component, so it applies wherever the component is used:

app.components.register({
  component: 'ServerStatus',
  renderer: { kind: 'function', render: ServerStatus },
  fallback: { component: 'text', content: 'status unavailable', fg: 'muted' },
});

A node fallback is handed the failure as the errorMessage and error props, so a registered component can render it. Props the fallback declares itself win, which is how you show something deliberately vague instead:

fallback: { component: 'ErrorPanel' }                       // gets errorMessage
fallback: { component: 'text', content: 'unavailable' }     // says nothing

For the local case, a fallback may instead be a function receiving the failure

  • the thrown value, its message, and which component threw:
const node = {
  component: 'ServerStatus',
  $meta: {
    fn: ServerStatus,
    fallback: (failure: RenderError) => ({
      component: 'text',
      content: `${failure.component} failed: ${failure.message}`,
      fg: 'danger',
    }),
  },
};

The same fallback answers a component name that was never registered - a miss and a throw are the same question, what does this render instead. Without one, a miss renders <ServerStatus> and a throw renders its message, both in danger, because a failure nobody can see is one nobody fixes.


Back to top

MIT licensed. Pre-1.0 - the surface is still moving.