Skip to content

Commit d25cf09

Browse files
author
Dion Low
committed
feat: add React Error Boundary to InstallIntegration component
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 <noreply@anthropic.com> lint: fix lint errors feat: add error boundary to install integration / polish
1 parent 5371fd3 commit d25cf09

5 files changed

Lines changed: 91 additions & 13 deletions

File tree

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* eslint-disable react/prop-types */
2+
import React, { Component, ReactNode } from "react";
3+
4+
import { ComponentContainerError } from "./ComponentContainer";
5+
6+
interface ErrorBoundaryProps {
7+
children: ReactNode;
8+
fallback?: ReactNode;
9+
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
10+
}
11+
12+
interface ErrorBoundaryState {
13+
hasError: boolean;
14+
error: Error | null;
15+
}
16+
17+
/**
18+
* React Error Boundary component that catches JavaScript errors anywhere in the child component tree
19+
* and displays a fallback UI instead of crashing the whole application.
20+
*
21+
* This is especially important for the InstallIntegration component to prevent errors in hooks
22+
* or rendering from crashing the parent application.
23+
*/
24+
export class InstallIntegrationErrorBoundary extends Component<
25+
ErrorBoundaryProps,
26+
ErrorBoundaryState
27+
> {
28+
constructor(props: ErrorBoundaryProps) {
29+
super(props);
30+
this.state = { hasError: false, error: null };
31+
}
32+
33+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
34+
// Update state so the next render will show the fallback UI
35+
return { hasError: true, error };
36+
}
37+
38+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
39+
// Log error to console for debugging
40+
console.error("InstallIntegration Error Boundary caught an error:", error);
41+
console.error("[Error Info]:", errorInfo);
42+
43+
// Call optional error callback
44+
this.props.onError?.(error, errorInfo);
45+
}
46+
47+
render(): ReactNode {
48+
if (this.state.hasError) {
49+
// Use custom fallback if provided, otherwise use default
50+
if (this.props.fallback) {
51+
return this.props.fallback;
52+
}
53+
54+
return (
55+
<ComponentContainerError
56+
message={
57+
this.state.error?.message ||
58+
"Something went wrong. Please try again later."
59+
}
60+
/>
61+
);
62+
}
63+
64+
return this.props.children;
65+
}
66+
}

src/components/Configure/InstallIntegration.tsx

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ComponentContainerLoading,
1414
} from "./ComponentContainer";
1515
import { InstallationContent } from "./content/InstallationContent";
16+
import { InstallIntegrationErrorBoundary } from "./ErrorBoundary";
1617
import { ConditionalHasConfigurationLayout } from "./layout/ConditionalHasConfigurationLayout/ConditionalHasConfigurationLayout";
1718
import { ProtectedConnectionLayout } from "./layout/ProtectedConnectionLayout";
1819
import { ObjectManagementNav } from "./nav/ObjectManagementNav";
@@ -165,15 +166,22 @@ export function InstallIntegration({
165166
};
166167

167168
return (
168-
// eventually will use the headless providers for integration, consumer, and group etc
169-
<InstallationProvider
170-
integration={integration}
171-
consumerRef={consumerRef}
172-
consumerName={consumerName}
173-
groupRef={groupRef}
174-
groupName={groupName}
169+
// catch errors in the InstallIntegrationContent component
170+
<InstallIntegrationErrorBoundary
171+
fallback={
172+
<ComponentContainerError message="Something went wrong, couldn't find integration information" />
173+
}
175174
>
176-
<InstallIntegrationContent {...props} />
177-
</InstallationProvider>
175+
{/* eventually will use the headless providers for integration, consumer, and group etc */}
176+
<InstallationProvider
177+
integration={integration}
178+
consumerRef={consumerRef}
179+
consumerName={consumerName}
180+
groupRef={groupRef}
181+
groupName={groupName}
182+
>
183+
<InstallIntegrationContent {...props} />
184+
</InstallationProvider>
185+
</InstallIntegrationErrorBoundary>
178186
);
179187
}

src/components/Configure/content/ConfigureInstallationBase.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,10 @@ export function ConfigureInstallationBase({
127127
const isStateNew = isModified || isCreateMode || isSelectedReadObjectComplete;
128128

129129
// if the selected read object has an error in the manifest, it should not be saved
130-
const isSelectedReadObjectError = !!hydratedRevision?.content?.read?.objects?.find(
131-
(obj) => obj.objectName === selectedObjectName,
132-
)?.error;
130+
const isSelectedReadObjectError =
131+
!!hydratedRevision?.content?.read?.objects?.find(
132+
(obj) => obj.objectName === selectedObjectName,
133+
)?.error;
133134

134135
// should the save button be disabled?
135136
const isDisabled =

src/components/Configure/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export { InstallIntegration } from "./InstallIntegration";
2+
export { InstallIntegrationErrorBoundary } from "./ErrorBoundary";

src/headless/installation/useCreateInstallation.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ export function useCreateInstallation() {
5050
onSettled?: () => void;
5151
}) => {
5252
if (installation) {
53-
const error = new Error("Installation already created. Try updating instead.");
53+
const error = new Error(
54+
"Installation already created. Try updating instead.",
55+
);
5456
onError?.(error);
5557
onSettled?.();
5658
return;

0 commit comments

Comments
 (0)