Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 44 additions & 30 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,54 +21,42 @@ import ProductDetailScreen from './screens/ProductDetailScreen';
import ReduxScreen from './screens/ReduxScreen';
import CartScreen from './screens/CartScreen';
import CheckoutScreen from './screens/CheckoutScreen';
import Toast from 'react-native-toast-message';

import {RootState, store, showFeedbackActionButton} from './reduxApp';
import {DSN} from './config';
import {RootState, store} from './reduxApp';
import {SE} from '@env'; // SE is undefined if no .env file is set
import {RootStackParamList} from './navigation';
import {GestureHandlerRootView} from 'react-native-gesture-handler';
import {LogBox, Platform, StyleSheet} from 'react-native';
import {LogBox, Platform, Pressable, StyleSheet, Text, View} from 'react-native';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import {SentryUserFeedbackActionButton} from './components/UserFeedbackModal';
import {DSN} from './config';
console.log('> SE', SE);

LogBox.ignoreAllLogs();

const reactNavigationIntegration =
Sentry.reactNavigationIntegration({
// How long it will wait for the route change to complete. Default is 1000ms
routeChangeTimeoutMs: 500,
enableTimeToInitialDisplay: true,
});
const reactNavigationIntegration = Sentry.reactNavigationIntegration({
routeChangeTimeoutMs: 500,
enableTimeToInitialDisplay: true,
});

// Get app version from package.json, for fingerprinting
const packageJson = require('../package.json');

Sentry.init({
dsn: DSN,
debug: true,
environment: 'dev',
enableLogs: true,
beforeSend: (event) => {
beforeSend: event => {
if (SE === 'tda') {
// Make issues unique to the release (app version) for Release Health
event.fingerprint = ['{{ default }}', SE, packageJson.version];
} else if (SE) {
// Make issue for the SE
event.fingerprint = ['{{ default }}', SE];
}

if (!event.type) {
// Only show the feedback button for errors
store.dispatch(showFeedbackActionButton());
}

return event;
},
integrations: [
Sentry.reactNativeTracingIntegration({
traceFetch: false, // RN uses XHR to implement fetch, this prevents duplicates
traceFetch: false,
}),
Sentry.mobileReplayIntegration({
maskAllImages: true,
Expand All @@ -83,8 +71,8 @@ Sentry.init({
replaysSessionSampleRate: 1.0,
enableUserInteractionTracing: true,
enableAutoSessionTracking: true,
sessionTrackingIntervalMillis: 5000, // For testing, session close when 5 seconds (instead of the default 30) in the background.
maxBreadcrumbs: 150, // Extend from the default 100 breadcrumbs.
sessionTrackingIntervalMillis: 5000,
maxBreadcrumbs: 150,
attachStacktrace: true,
attachScreenshot: true,
attachViewHierarchy: true,
Expand All @@ -93,6 +81,15 @@ Sentry.init({

Sentry.setTag('se', SE);

const FallbackComponent = ({resetError}: {resetError: () => void}) => (
<View style={styles.fallback}>
<Text style={styles.fallbackText}>Something went wrong.</Text>
<Pressable style={styles.fallbackButton} onPress={resetError}>
<Text style={styles.fallbackButtonText}>Refresh</Text>
</Pressable>
</View>
);

const Tab = createBottomTabNavigator();

const Stack = createStackNavigator<RootStackParamList>();
Expand All @@ -116,10 +113,10 @@ const App = () => {
customerType,
email,
se: SE,
version: packageJson.version,
});

return (
<Sentry.ErrorBoundary fallback={FallbackComponent}>
<Provider store={store}>
<SafeAreaProvider>
<GestureHandlerRootView style={styles.gestureHandlerRootView}>
Expand All @@ -138,6 +135,7 @@ const App = () => {
</GestureHandlerRootView>
</SafeAreaProvider>
</Provider>
</Sentry.ErrorBoundary>
);
};

Expand Down Expand Up @@ -242,11 +240,27 @@ const styles = StyleSheet.create({
gestureHandlerRootView: {
flex: 1,
},
});

export default Sentry.wrap(App, {
touchEventBoundaryProps: {
ignoreNames: ['Provider', 'UselessName', /^SomeRegex/],
labelName: 'id',
fallback: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#fff',
},
fallbackText: {
fontSize: 18,
marginBottom: 20,
color: '#000',
},
fallbackButton: {
paddingHorizontal: 24,
paddingVertical: 12,
backgroundColor: '#002626',
borderRadius: 8,
},
fallbackButtonText: {
color: '#fff',
fontWeight: 'bold',
},
});

export default Sentry.wrap(App);
90 changes: 90 additions & 0 deletions src/components/ErrorBoundaryProduct.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome6';
import {StyledButton} from './StyledButton';

const BuggyCart = (): React.ReactElement => {
return (
<StyledButton
testID="add-to-cart-button-error-product"
title="Add to cart"
onPress={() => {
throw new Error('Error boundary triggered from error product card');
}}
Comment on lines +11 to +13
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The error thrown in the onPress handler is not caught by Sentry.ErrorBoundary, as error boundaries do not capture errors from event handlers, causing an unhandled exception.
Severity: CRITICAL

Suggested Fix

To correctly handle the error and prevent an app crash, wrap the code inside the onPress event handler with a try/catch block. This will allow you to catch the exception and manage the application state or UI accordingly, without relying on the React Error Boundary for event-driven errors.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/components/ErrorBoundaryProduct.tsx#L11-L13

Potential issue: The error thrown within the `onPress` event handler in
`ErrorBoundaryProduct.tsx` will not be caught by the `Sentry.ErrorBoundary` component.
React error boundaries only capture errors that occur during the render phase, in
lifecycle methods, or in constructors; they do not handle errors from event handlers. As
a result, interacting with this component will cause an unhandled JavaScript exception,
leading to a potential app crash in production instead of displaying the intended
fallback UI.

style={{
default: styles.addToCartButton,
pressed: styles.addToCartButton,
}}
/>
);
};

export const ErrorBoundaryProduct = (): React.ReactElement => {
return (
<View style={styles.cardContainer}>
<View style={styles.cardHero}>
<Icon name="triangle-exclamation" size={48} color="#e74c3c" />
</View>
<View style={styles.cardDetail}>
<View style={styles.cardDetailContent}>
<Text style={styles.cardTitle}>Error Product</Text>
<Text style={styles.cardDescription}>Boundary error testing</Text>
</View>
<View style={styles.cardDetailAction}>
<BuggyCart />
</View>
</View>
</View>
);
};

const styles = StyleSheet.create({
cardContainer: {
width: '100%',
height: 200,
borderWidth: 1,
borderColor: '#e74c3c',
borderRadius: 6,
backgroundColor: '#ffffff',
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginVertical: 5,
},
cardHero: {
width: '40%',
height: '100%',
backgroundColor: '#fdf0f0',
alignItems: 'center',
justifyContent: 'center',
},
cardDetail: {
flex: 1,
height: '100%',
flexDirection: 'column',
},
cardDetailContent: {
padding: 10,
flex: 1,
flexDirection: 'column',
justifyContent: 'space-between',
paddingBottom: 10,
},
cardDetailAction: {
flex: 0,
},
cardTitle: {
marginBottom: 5,
fontSize: 24,
fontWeight: '500',
color: '#c0392b',
},
cardDescription: {
fontSize: 14,
color: '#555',
},
addToCartButton: {
margin: 10,
},
});
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const DSN =
'https://b87682e62e4cc633d4c35c7154256c66@sandbox-mirror.sentry.gg/1';
'https://6d107379901d7674dce67c2cf3a735fe@o88872.ingest.us.sentry.io/4511095053615104';

// SENTRY_INTERNAL_DSN for testing
// export const DSN =
Expand Down
2 changes: 2 additions & 0 deletions src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {BACKEND_URL} from '../config';
import {StackScreenProps} from '@react-navigation/stack';
import {RootStackParamList} from '../navigation';
import {ProfiledStyledProductCard} from '../components/StyledProductCard';
import {ErrorBoundaryProduct} from '../components/ErrorBoundaryProduct';
import {Product} from '../types/Product';

type ExtendedSentryScope = Sentry.Scope & {
Expand Down Expand Up @@ -108,6 +109,7 @@ const EmpowerPlant = ({navigation}: StackScreenProps<RootStackParamList>) => {
refreshing={toolData === null}
data={toolData}
contentContainerStyle={styles.productListContainer}
ListHeaderComponent={toolData ? <ErrorBoundaryProduct /> : null}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The ErrorBoundaryProduct component is rendered without a React Error Boundary, causing an unhandled error that will crash the app when the 'Add to cart' button is pressed.
Severity: CRITICAL

Suggested Fix

Wrap the ErrorBoundaryProduct component instance in src/screens/HomeScreen.tsx with <Sentry.ErrorBoundary> and provide a fallback UI component to gracefully handle the intentional error, preventing the application from crashing.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: src/screens/HomeScreen.tsx#L112

Potential issue: The `ErrorBoundaryProduct` component is designed to throw an error when
the user presses the "Add to cart" button. However, this component is rendered as a
`ListHeaderComponent` in the `FlatList` without being wrapped in a React Error Boundary.
When the button is pressed, the resulting unhandled render error will propagate to the
React root and crash the entire application. The existing `Sentry.wrap(App)` is for
performance tracing and does not catch render errors, and a `SentryProvider` component
with a proper error boundary exists but is not used.

Did we get this right? 👍 / 👎 to inform future reviews.

renderItem={({item}) => {
return (
<ProfiledStyledProductCard
Expand Down
Loading