shadimage
nteract
liberror-boundarysource: ok · hit

Error Boundary

React error boundary with resetKeys support for automatic recovery. Includes OutputErrorFallback and WidgetErrorFallback components for graceful error display in outputs and widgets.

npx shadcn add "https://nteract-elements.vercel.app/r/error-boundary.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Files3 · source cached 10/14/2026, 3:38:59 PM

registry/lib/error-boundary.tsx
"use client";

import { Component, type ErrorInfo, type ReactNode } from "react";

export interface ErrorBoundaryProps {
  children: ReactNode;
  /** Render prop called when an error is caught. Return the fallback UI. */
  fallback: (error: Error, resetErrorBoundary: () => void) => ReactNode;
  /** Optional callback when an error is caught. */
  onError?: (error: Error, errorInfo: ErrorInfo) => void;
  /**
   * When any value in this array changes, the error state is automatically
   * cleared and the children are re-rendered. Use this to recover from
   * errors when the underlying data changes (e.g., cell re-execution,
   * new output data, widget state update).
   */
  resetKeys?: readonly unknown[];
}

interface ErrorBoundaryState {
  error: Error | null;
  prevResetKeys: readonly unknown[] | undefined;
}

/**
 * Reusable React error boundary.
 *
 * Catches render errors in child components and displays a fallback UI
 * via the `fallback` render prop. Provides a `resetErrorBoundary` function
 * to retry rendering.
 *
 * Supports automatic recovery via `resetKeys` — when any key changes,
 * the error state is cleared and children re-render.
 *
 * @example
 * ```tsx
 * <ErrorBoundary
 *   fallback={(error, reset) => (
 *     <div>
 *       <p>Something went wrong: {error.message}</p>
 *       <button onClick={reset}>Retry</button>
 *     </div>
 *   )}
 *   resetKeys={[executionCount]}
 * >
 *   <MyComponent />
 * </ErrorBoundary>
 * ```
 */
export class ErrorBoundary extends Component<
  ErrorBoundaryProps,
  ErrorBoundaryState
> {
  constructor(props: ErrorBoundaryProps) {
    super(props);
    this.state = { error: null, prevResetKeys: props.resetKeys };
  }

  static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
    return { error };
  }

  static getDerivedStateFromProps(
    props: ErrorBoundaryProps,
    state: ErrorBoundaryState,
  ): Partial<ErrorBoundaryState> | null {
    if (
      state.error &&
      state.prevResetKeys !== undefined &&
      props.resetKeys !== undefined
    ) {
      const changed =
        props.resetKeys.length !== state.prevResetKeys.length ||
        props.resetKeys.some(
          (key, i) => !Object.is(key, state.prevResetKeys![i]),
        );
      if (changed) {
        return { error: null, prevResetKeys: props.resetKeys };
      }
    }
    // Always track latest resetKeys even when not in error state
    if (props.resetKeys !== state.prevResetKeys) {
      return { prevResetKeys: props.resetKeys };
    }
    return null;
  }

  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
    console.error("[ErrorBoundary]", error, errorInfo.componentStack);
    this.props.onError?.(error, errorInfo);
  }

  resetErrorBoundary = () => {
    this.setState({ error: null });
  };

  render() {
    if (this.state.error) {
      return this.props.fallback(this.state.error, this.resetErrorBoundary);
    }
    return this.props.children;
  }
}
registry/lib/output-error-fallback.tsx
"use client";

import { cn } from "@/lib/utils";

export interface OutputErrorFallbackProps {
  /** The error that was caught */
  error: Error;
  /** Optional index of the output (0-based) for display */
  outputIndex?: number;
  /** Callback to retry rendering */
  onRetry?: () => void;
}

/**
 * Fallback UI for output rendering errors.
 *
 * Displays an error message with optional retry button.
 * Styled to match AnsiErrorOutput patterns.
 */
export function OutputErrorFallback({
  error,
  outputIndex,
  onRetry,
}: OutputErrorFallbackProps) {
  return (
    <div
      data-slot="output-error"
      className={cn(
        "border-l-2 border-red-200 dark:border-red-800 py-3 pl-3 pr-2",
        "rounded-r bg-red-50/50 dark:bg-red-950/20",
      )}
    >
      <div className="flex items-start justify-between gap-2">
        <div>
          <div className="text-sm font-medium text-red-700 dark:text-red-400">
            Output rendering failed
            {outputIndex !== undefined && ` (output ${outputIndex + 1})`}
          </div>
          <div className="mt-1 font-mono text-xs text-red-600/80 dark:text-red-400/70">
            {error.message}
          </div>
        </div>
        {onRetry && (
          <button
            type="button"
            onClick={onRetry}
            className="text-xs text-red-600 underline hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
          >
            Retry
          </button>
        )}
      </div>
    </div>
  );
}
registry/lib/widget-error-fallback.tsx
import { cn } from "@/lib/utils";

export interface WidgetErrorFallbackProps {
  /** The error that was caught */
  error: Error;
  /** The widget model ID */
  modelId: string;
  /** Optional widget model name for display */
  modelName?: string;
  /** Callback to retry rendering */
  onRetry?: () => void;
}

/**
 * Fallback UI for widget rendering errors.
 *
 * Displays an error message with optional retry button.
 * Styled to match UnsupportedWidget patterns.
 */
export function WidgetErrorFallback({
  error,
  modelId,
  modelName,
  onRetry,
}: WidgetErrorFallbackProps) {
  return (
    <div
      className={cn(
        "rounded border border-dashed border-red-300 p-3 text-sm dark:border-red-800",
        "bg-red-50/50 dark:bg-red-950/20",
      )}
      data-widget-id={modelId}
      data-widget-error="true"
    >
      <div className="flex items-start justify-between gap-2">
        <div>
          <div className="font-medium text-red-700 dark:text-red-400">
            Widget error{modelName && `: ${modelName}`}
          </div>
          <div className="mt-1 font-mono text-xs text-red-600/70 dark:text-red-400/60">
            {error.message}
          </div>
        </div>
        {onRetry && (
          <button
            type="button"
            onClick={onRetry}
            className="text-xs text-red-600 underline hover:text-red-800 dark:text-red-400 dark:hover:text-red-300"
          >
            Retry
          </button>
        )}
      </div>
    </div>
  );
}

More from nteract

Browse all components in this library →