Skip to content

Error Handling

React has Error Boundaries. Solid has <ErrorBoundary>. You might expect Rimitive to have something similar.

It doesn’t—because it doesn’t need one.


In React and Solid, components are reactive functions that re-run during a render cycle. Errors can happen mid-render, and the framework needs a way to catch them and show fallback UI.

In Rimitive, components are just functions that return specs. They run once, produce a data structure, and that’s it. Errors are plain JavaScript errors that propagate normally.

const RiskyComponent = (svc: Service) => {
const { el } = svc;
// If this throws, it's a normal JS error
const data = somethingThatMightThrow();
return el('div')(data.value);
};

You handle it with… try/catch:

const SafeWrapper = (svc: Service) => {
const { el } = svc;
try {
return RiskyComponent(svc);
} catch (e) {
return el('div').props({ className: 'error' })(
'Something went wrong'
);
}
};

For async operations, use the resource primitive. It tracks error state explicitly:

const ProductList = (svc: Service) => {
const { el, resource, match } = svc;
const products = resource((signal) =>
fetch('/api/products', { signal }).then(r => r.json())
);
return match(products, (state) => {
if (state.status === 'pending') {
return el('div')('Loading...');
}
if (state.status === 'error') {
return el('div').props({ className: 'error' })(
`Failed to load: ${state.error}`
);
}
return el('ul')(
...state.value.map(p => el('li')(p.name))
);
});
};

The error state is part of the resource’s reactive value.


Errors in effects propagate normally. If you need to catch them:

import { compose } from '@rimitive/core';
import { SignalModule, EffectModule } from '@rimitive/signals/extend';
const svc = compose(SignalModule, EffectModule)();
const { signal, effect } = svc;
const count = signal(0);
effect(() => {
try {
riskyOperation(count());
} catch (e) {
console.error('Effect failed:', e);
// Handle gracefully
}
});

Or wrap the risky operation in a function that returns a result type:

type Result<T> = { ok: true; value: T } | { ok: false; error: unknown };
const safeRiskyOperation = (n: number): Result<string> => {
try {
return { ok: true, value: riskyOperation(n) };
} catch (e) {
return { ok: false, error: e };
}
};
effect(() => {
const result = safeRiskyOperation(count());
if (!result.ok) {
errorState(result.error);
}
});

If you want an Error Boundary-like pattern, create a behavior:

const errorBoundary = (svc: Service) =>
<T>(
render: () => T,
fallback: (error: unknown) => T
): T => {
try {
return render();
} catch (e) {
return fallback(e);
}
};
// Usage
const App = (svc: Service) => {
const { el, use } = svc;
const withErrorBoundary = use(errorBoundary);
return el('div')(
withErrorBoundary(
() => RiskyComponent(svc),
(e) => el('div')(`Error: ${e}`)
)
);
};

But honestly? Just use try/catch. It’s JavaScript. It works.