From d25cf097194add63fad5df5afd6f864142e0c8a6 Mon Sep 17 00:00:00 2001 From: Dion Low Date: Tue, 21 Oct 2025 14:43:33 -0700 Subject: [PATCH] feat: add React Error Boundary to InstallIntegration component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a proper React Error Boundary component to wrap InstallIntegration, preventing rendering errors or hook errors from crashing the parent application. Changes: - Created InstallIntegrationErrorBoundary class component that catches JavaScript errors - Wrapped InstallIntegration component with error boundary - Error boundary displays ComponentContainerError on failure - Exported InstallIntegrationErrorBoundary for optional standalone use - Added optional onError callback prop for custom error handling Benefits: - Prevents InstallIntegration errors from crashing parent app - Provides graceful fallback UI when errors occur - Logs errors to console for debugging - Works as final safety net alongside the error callback improvements from previous PRs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude lint: fix lint errors feat: add error boundary to install integration / polish --- src/components/Configure/ErrorBoundary.tsx | 66 +++++++++++++++++++ .../Configure/InstallIntegration.tsx | 26 +++++--- .../content/ConfigureInstallationBase.tsx | 7 +- src/components/Configure/index.ts | 1 + .../installation/useCreateInstallation.ts | 4 +- 5 files changed, 91 insertions(+), 13 deletions(-) create mode 100644 src/components/Configure/ErrorBoundary.tsx diff --git a/src/components/Configure/ErrorBoundary.tsx b/src/components/Configure/ErrorBoundary.tsx new file mode 100644 index 000000000..4552003a6 --- /dev/null +++ b/src/components/Configure/ErrorBoundary.tsx @@ -0,0 +1,66 @@ +/* eslint-disable react/prop-types */ +import React, { Component, ReactNode } from "react"; + +import { ComponentContainerError } from "./ComponentContainer"; + +interface ErrorBoundaryProps { + children: ReactNode; + fallback?: ReactNode; + onError?: (error: Error, errorInfo: React.ErrorInfo) => void; +} + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | null; +} + +/** + * React Error Boundary component that catches JavaScript errors anywhere in the child component tree + * and displays a fallback UI instead of crashing the whole application. + * + * This is especially important for the InstallIntegration component to prevent errors in hooks + * or rendering from crashing the parent application. + */ +export class InstallIntegrationErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + // Update state so the next render will show the fallback UI + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + // Log error to console for debugging + console.error("InstallIntegration Error Boundary caught an error:", error); + console.error("[Error Info]:", errorInfo); + + // Call optional error callback + this.props.onError?.(error, errorInfo); + } + + render(): ReactNode { + if (this.state.hasError) { + // Use custom fallback if provided, otherwise use default + if (this.props.fallback) { + return this.props.fallback; + } + + return ( + + ); + } + + return this.props.children; + } +} diff --git a/src/components/Configure/InstallIntegration.tsx b/src/components/Configure/InstallIntegration.tsx index af51ce3b5..6e13fb13e 100644 --- a/src/components/Configure/InstallIntegration.tsx +++ b/src/components/Configure/InstallIntegration.tsx @@ -13,6 +13,7 @@ import { ComponentContainerLoading, } from "./ComponentContainer"; import { InstallationContent } from "./content/InstallationContent"; +import { InstallIntegrationErrorBoundary } from "./ErrorBoundary"; import { ConditionalHasConfigurationLayout } from "./layout/ConditionalHasConfigurationLayout/ConditionalHasConfigurationLayout"; import { ProtectedConnectionLayout } from "./layout/ProtectedConnectionLayout"; import { ObjectManagementNav } from "./nav/ObjectManagementNav"; @@ -165,15 +166,22 @@ export function InstallIntegration({ }; return ( - // eventually will use the headless providers for integration, consumer, and group etc - + } > - - + {/* eventually will use the headless providers for integration, consumer, and group etc */} + + + + ); } diff --git a/src/components/Configure/content/ConfigureInstallationBase.tsx b/src/components/Configure/content/ConfigureInstallationBase.tsx index e637eb135..b63b72c67 100644 --- a/src/components/Configure/content/ConfigureInstallationBase.tsx +++ b/src/components/Configure/content/ConfigureInstallationBase.tsx @@ -127,9 +127,10 @@ export function ConfigureInstallationBase({ const isStateNew = isModified || isCreateMode || isSelectedReadObjectComplete; // if the selected read object has an error in the manifest, it should not be saved - const isSelectedReadObjectError = !!hydratedRevision?.content?.read?.objects?.find( - (obj) => obj.objectName === selectedObjectName, - )?.error; + const isSelectedReadObjectError = + !!hydratedRevision?.content?.read?.objects?.find( + (obj) => obj.objectName === selectedObjectName, + )?.error; // should the save button be disabled? const isDisabled = diff --git a/src/components/Configure/index.ts b/src/components/Configure/index.ts index ae00fa05c..05f1b8fad 100644 --- a/src/components/Configure/index.ts +++ b/src/components/Configure/index.ts @@ -1 +1,2 @@ export { InstallIntegration } from "./InstallIntegration"; +export { InstallIntegrationErrorBoundary } from "./ErrorBoundary"; diff --git a/src/headless/installation/useCreateInstallation.ts b/src/headless/installation/useCreateInstallation.ts index 34f58a7cb..32f56ba3c 100644 --- a/src/headless/installation/useCreateInstallation.ts +++ b/src/headless/installation/useCreateInstallation.ts @@ -50,7 +50,9 @@ export function useCreateInstallation() { onSettled?: () => void; }) => { if (installation) { - const error = new Error("Installation already created. Try updating instead."); + const error = new Error( + "Installation already created. Try updating instead.", + ); onError?.(error); onSettled?.(); return;