react-resilience-kit
v0.2.0
Published
Enterprise-grade React error resilience: async errors, promise rejections, retry, recovery and logging with zero boilerplate.
Maintainers
Readme
react-resilience-kit
React resilience primitives for render failures, browser errors, rejected promises, async operations, retries, recovery, and pluggable error reporting.
Requirements
- React 18 or newer
- Node.js 16 or newer for package tooling
Install
npm install react-resilience-kitQuick start
ResilienceProvider installs a root render boundary, captures browser errors and unhandled promise rejections, and enables the built-in console adapter.
import { ResilienceProvider } from "react-resilience-kit";
root.render(
<ResilienceProvider>
<App />
</ResilienceProvider>,
);The default fallback displays the error message and provides a reset button. Use ErrorGuard to isolate a smaller part of the tree or to provide a different fallback:
import { ErrorGuard } from "react-resilience-kit";
function App() {
return (
<ErrorGuard>
<Dashboard />
</ErrorGuard>
);
}The provider catches render, lifecycle, and constructor errors from its children. A boundary cannot catch errors thrown inside event handlers; use useAsyncGuard or useErrorReporter for those operations.
Fallback UI
Use the built-in minimal preset, a React component receiving error and reset, or a React element:
function AppFallback({ error, reset }: { error: unknown; reset: () => void }) {
return (
<section role="alert">
<h1>Dashboard unavailable</h1>
<p>{error instanceof Error ? error.message : "Unknown error"}</p>
<button type="button" onClick={reset}>
Try again
</button>
</section>
);
}
<ResilienceProvider fallback={AppFallback}>
<App />
</ResilienceProvider>;Custom fallback text and actions
The package keeps sensible defaults, but consumers can override the default labels and content without replacing the whole fallback component. fallbackOptions accepts a small UI config and is merged with the built-in defaults.
<ResilienceProvider
fallbackOptions={{
title: "Dashboard unavailable",
message: (error) => (error instanceof Error ? error.message : "Unexpected dashboard error"),
retryLabel: "Retry dashboard data",
resetLabel: "Reset dashboard",
reloadLabel: "Refresh page",
}}
>
<App />
</ResilienceProvider>You can also pass a custom fallback component and still use the same configuration object:
function AppFallback({ error, reset, retry, reload, ui }: FallbackProps) {
return (
<section role="alert">
<h2>{ui?.title ?? "Something went wrong"}</h2>
<p>{typeof ui?.message === "function" ? ui.message(error) : ui?.message}</p>
{retry && <button onClick={retry}>{ui?.retryLabel ?? "Try again"}</button>}
<button onClick={reset}>{ui?.resetLabel ?? "Reset"}</button>
{reload && <button onClick={reload}>{ui?.reloadLabel ?? "Reload page"}</button>}
</section>
);
}
<ResilienceProvider fallback={AppFallback} fallbackOptions={{ title: "Dashboard unavailable" }}>
<App />
</ResilienceProvider>;Async operations
useAsyncGuard reports a rejected operation and rethrows the original error, so the calling component can still update its own loading and error state.
import { useAsyncGuard } from "react-resilience-kit";
function Users() {
const { execute } = useAsyncGuard();
async function loadUsers() {
return execute(
() =>
fetch("/api/users").then((response) => {
if (!response.ok) throw new Error("Unable to load users");
return response.json();
}),
{ zone: "users" },
);
}
return (
<button type="button" onClick={() => void loadUsers()}>
Load users
</button>
);
}The library does not force a failure message or button label. Apps can own that UI completely and keep their own copy, for example:
const uiLabels = {
loading: "Loading team members...",
error: "Unable to load team members after retries.",
retry: "Retry team data",
primary: "Load team members",
};
// The app decides exactly what to show when the async call fails.Retry
Retries can be configured for the whole provider or for one operation. attempts includes the initial invocation.
<ResilienceProvider retry={{ attempts: 3, strategy: "exponential", delay: 500 }}>
<App />
</ResilienceProvider>const result = await execute(fetchUsers, {
retry: { attempts: 4, strategy: "fibonacci", delay: 250 },
});Available strategies:
linear: waits the base delay before each retry.exponential: waitsdelay,2 * delay,4 * delay, and so on.fibonacci: waits Fibonacci multiples of the base delay.custom: callsdelayStrategy(attempt)and expects milliseconds.
The lower-level helper is also available:
import { executeWithRetry } from "react-resilience-kit";
const value = await executeWithRetry(fetchUsers, {
attempts: 3,
strategy: "linear",
delay: 500,
});Reporting adapters
The console adapter is active by default. Supply a custom adapter through reporting.adapter:
const myLogger = {
captureError(error, meta) {
sendToMonitoring({ error, meta });
},
captureMessage(message, meta) {
sendToMonitoring({ message, meta });
},
identifyUser(userId, traits) {
sendUserToMonitoring(userId, traits);
},
flush() {
return Promise.resolve();
},
};
<ResilienceProvider reporting={{ adapter: myLogger }}>
<App />
</ResilienceProvider>;An adapter implements:
interface LoggerAdapter {
captureError(error: unknown, meta: ErrorMeta): void;
captureMessage(message: string, meta?: Partial<ErrorMeta>): void;
identifyUser(userId: string, traits?: Record<string, unknown>): void;
flush(): Promise<void>;
}Only one active adapter is used by the provider. consoleAdapter is exported for direct use or testing.
Manual reporting
import { useErrorReporter } from "react-resilience-kit";
function SaveButton() {
const report = useErrorReporter();
async function save() {
try {
await saveChanges();
} catch (error) {
report(error, { route: "/settings", extra: { action: "save" } });
}
}
return (
<button type="button" onClick={() => void save()}>
Save
</button>
);
}Supported metadata includes route, zone, componentStack, timestamp, source, and extra. The library supplies timestamp and source automatically; manual reports use source: "manual".
Recovery actions
import { useRecovery } from "react-resilience-kit";
function RecoveryControls() {
const { retry, reset, reload } = useRecovery();
return (
<>
<button type="button" onClick={retry}>
Retry request
</button>
<button type="button" onClick={reset}>
Reset boundary
</button>
<button type="button" onClick={reload}>
Reload page
</button>
</>
);
}retryreruns the most recent operation started withuseAsyncGuard.resetresets the most recently mountedErrorGuard.reloadcallswindow.location.reload()and has no effect during server-side rendering.
Public API
ResilienceProvider,ResilienceProviderPropsErrorGuard,ErrorGuardProps,MinimalFallbackuseAsyncGuard,UseAsyncGuardOptionsuseRecovery,useErrorReporter,useResilienceContextexecuteWithRetry,linearDelay,exponentialDelay,fibonacciDelayconsoleAdapter,ErrorManager,RecoveryManager- Public types:
ErrorMeta,LoggerAdapter,RetryConfig,RetryStrategy,ReportingConfig,ResilienceConfig,FallbackProps,FallbackPreset,FallbackUIConfig, andErrorSubscriber
Current scope and limitations
This is the Phase 1 MVP. analytics and autoChunkRecovery are accepted provider options for forward compatibility but are currently no-ops. Error zones, automatic chunk recovery, analytics, and third-party adapters are not included in this release.
Global browser handlers are installed when ResilienceProvider mounts and removed when it unmounts. The library does not prevent the browser's normal error behavior or automatically suppress rejected promises.
Validate before publishing
From the package directory, run:
npm install
npm run typecheck
npm run lint
npm run build
npm pack --dry-runThe package should contain the compiled dist files, README.md, LICENSE, and package.json. Do not commit or publish node_modules.
Publish to npm step by step
Create an npm account at https://www.npmjs.com/signup and verify the email address.
Open a terminal in this package directory.
Check the package name is available:
npm view react-resilience-kit name versionIf npm returns a published package, choose a different name or request access to that package.
Log in interactively:
npm login npm whoamiReview the package contents and metadata:
npm pack --dry-run npm pkg get name version main module types exports filesRun the validation commands in the previous section.
Increment the version. npm will reject publishing a version that already exists:
npm version patchUse
npm version minorfor a backward-compatible feature ornpm version majorfor a breaking API change.Publish the public package:
npm publish --access publicVerify the published package:
npm view react-resilience-kit version npm install react-resilience-kit
Never put an npm token in source control or command history. For CI publishing, use npm trusted publishing or an npm automation token stored in the CI secret store.
License
MIT
