react-swc-suspense-tracker
v0.5.0
Published
A development tool that tracks which React Suspense boundary catches thrown promises for easier debugging
Readme
react-swc-suspense-tracker
A development tool that helps you track which React Suspense and Error boundaries handle thrown promises and errors, making it easier to debug your React applications that use Suspense for data fetching and Error boundaries for error handling.
The Problem
React Suspense uses thrown Promises to pause rendering until data is ready, and Error boundaries catch thrown errors to handle failures gracefully. However, there's no public API to identify which boundary catches a suspension or error. This makes debugging difficult when you have multiple nested boundaries and need to understand the flow of suspensions and errors in your app.
The Solution
This package provides:
- SWC Plugin: Automatically replaces
SuspenseandErrorBoundaryimports to use trackable versions - Development Hooks: Utilities to detect missing boundaries and debug suspension/error flow
Installation
npm install --save-dev react-swc-suspense-trackerUsage
Next.js Setup
Add the plugin to your next.config.js:
module.exports = {
experimental: {
swcPlugins: [
[
"react-swc-suspense-tracker/swc",
{
// Optional: track custom boundary components
boundaries: [
{ component: "ErrorBoundary", from: "react-error-boundary" }
]
}
],
],
},
};*Note: The plugin is enabled by default in development mode and disabled in production builds. You can override this behavior by setting the enabled option.
Plugin Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| enabled | boolean | true on development false in production | Enable/disable the plugin transformation |
| boundaries | Array<{component: string, from: string}> | [] | Additional boundary components to track (e.g., custom Error boundaries) |
Using with SWC directly
Add to your .swcrc:
{
"jsc": {
"experimental": {
"plugins": [
[
"react-swc-suspense-tracker/swc",
{
"boundaries": [
{ "component": "ErrorBoundary", "from": "react-error-boundary" }
]
}
]
]
}
}
}Debugging Suspense Boundaries
The following example shows how you can debug specific hooks that might suspend.
Note: By default suspenseInfo will always be null in production mode.
To change that you have to set the enabled option to true in the SWC plugin configuration.
import { useQuery } from 'react-query';
import { wrapSuspendableHook } from 'react-swc-suspense-tracker';
const useQueryWithDebug = process.env.NODE_ENV === 'production'
? useQuery
: wrapSuspendableHook(
useQuery,
(suspenseBoundaries, queryKey) => {
if (suspenseBoundaries.length === 0) {
console.warn(`Suspense triggered by ${queryKey} but no Suspense boundary found`);
} else {
console.info(`Suspense triggered by ${queryKey} for boundary: ${suspenseBoundaries[0]}`);
}
}
);
function MyComponent() {
const { data } = useQueryWithDebug('my-query-key', fetchData);
return <div>{data}</div>;
}Throwing Errors for Missing Suspense Boundaries
If you want to ensure that your component is wrapped in a Suspense boundary, you can use the useThrowIfSuspenseMissing hook.
This will throw an error in development if the component might suspend but has no Suspense boundary above it.
import { useThrowIfSuspenseMissing } from "react-swc-suspense-tracker";
function MyComponent() {
// This will throw an error in development if no Suspense boundary is found
useThrowIfSuspenseMissing();
const data = useSomeDataHook(); // This might suspend
return <div>{data}</div>;
}Result:
What the SWC Plugin does
The plugin automatically transforms:
// Before transformation
import { Suspense } from "react";
<Suspense fallback={<Loading />}>
<MyComponent />
</Suspense>
// After transformation
import { Suspense } from "react";
import { BoundaryTrackerSWC } from "react-swc-suspense-tracker/context";
<BoundaryTrackerSWC Component={Suspense} fallback={<Loading />} id="my/file.tsx:123">
<MyComponent />
</BoundaryTrackerSWC>Custom logger
For custom logging or logging in production you can use the useSuspenseOwner hook to get the ID of the nearest Suspense boundary:
import {
useSuspenseOwner,
} from "react-swc-suspense-tracker";
function MyComponent() {
const suspenseOwner = useSuspenseOwner();
// Log the Suspense boundary ID
console.log("Closest Suspense boundary ID:", suspenseOwner);
const data = useSomeDataHook(); // This might suspend
return <div>{data}</div>;
}API Reference
Hooks
useSuspenseOwner(): string | null
Returns the ID of the nearest Suspense boundary above this component. The ID format is "file.tsx:line" if set by the SWC plugin, or a custom string if set manually.
Returns null if no Suspense boundary is found.
useBoundaryStack(): Array<[string, ComponentType]>
Returns information about all boundary components above this component as an array of [boundaryId, BoundaryComponent] tuples, ordered from outermost to innermost boundary.
Returns empty array if no boundaries are found.
useThrowIfSuspenseMissing(skip?: boolean): void
Throws an error in development if this component might suspend but has no Suspense boundary above it - NOOP in production builds
Parameters:
skip(optional): Iftrue, skips the check. Defaults tofalse.
Throws: Error with message explaining the missing Suspense boundary.
wrapSuspendableHook<T>(hook: T, onSuspense: (suspenseBoundaries: string[], ...args: Parameters<T>) => void): T
Wraps a hook to catch Suspense errors and call the provided onSuspense function with the current Suspense boundary information.
Parameters:
hook: The hook function to wraponSuspense: Function called when the hook suspends, receives array of Suspense boundary IDs
Returns: A wrapped version of the hook with the same signature.
Development vs Production
This package is designed for development use only. The SWC plugin should be disabled in production builds to avoid the additional runtime overhead.
Build
Get the Rust toolchain and the right target:
rustup target add wasm32-wasip1
npm run buildThe compiled Wasm module will be available as react_swc_suspense_tracker.wasm.
