55 StyleSheet ,
66 Text ,
77 View ,
8+ Button ,
89} from 'react-native' ;
910import {
1011 Fit ,
@@ -16,12 +17,55 @@ import {
1617import { Picker } from '@react-native-picker/picker' ;
1718import { type Metadata } from '../helpers/metadata' ;
1819
20+ class ErrorBoundary extends React . Component <
21+ {
22+ children : React . ReactNode ;
23+ fallback ?: ( error : Error , reset : ( ) => void ) => React . ReactNode ;
24+ } ,
25+ { hasError : boolean ; error : Error | null }
26+ > {
27+ constructor ( props : any ) {
28+ super ( props ) ;
29+ this . state = { hasError : false , error : null } ;
30+ }
31+
32+ static getDerivedStateFromError ( error : Error ) {
33+ return { hasError : true , error } ;
34+ }
35+
36+ componentDidCatch ( error : Error , errorInfo : React . ErrorInfo ) {
37+ console . error ( 'ErrorBoundary caught:' , error , errorInfo ) ;
38+ }
39+
40+ reset = ( ) => {
41+ this . setState ( { hasError : false , error : null } ) ;
42+ } ;
43+
44+ render ( ) {
45+ if ( this . state . hasError && this . state . error ) {
46+ if ( this . props . fallback ) {
47+ return this . props . fallback ( this . state . error , this . reset ) ;
48+ }
49+ return (
50+ < View style = { styles . errorContainer } >
51+ < Text style = { styles . errorText } > Something went wrong:</ Text >
52+ < Text style = { styles . errorMessage } > { this . state . error . message } </ Text >
53+ < Button title = "Try Again" onPress = { this . reset } />
54+ </ View >
55+ ) ;
56+ }
57+
58+ return this . props . children ;
59+ }
60+ }
61+
1962const delay = 1000 ;
2063
2164const ImageURL1 = `https://picsum.photos/id/372/500/500` as const ;
2265const ImageURL2 = `https://picsum.photos/id/373/500/500` as const ;
2366const ImageURLSlow =
2467 `https://app.requestly.io/delay/${ delay } /https://picsum.photos/id/374/500/500` as const ;
68+ const ImageInvalidURL = 'not-a-valid-url' as const ;
2569
2670type ImageURLS = typeof ImageURL1 | typeof ImageURL2 | typeof ImageURLSlow ;
2771
@@ -73,21 +117,48 @@ function RiveContent({ imageUrl }: { imageUrl: ImageURLS }) {
73117 ) ;
74118}
75119
120+ function ErrorFallback ( { error, reset } : { error : Error ; reset : ( ) => void } ) {
121+ return (
122+ < View style = { styles . errorContainer } >
123+ < Text style = { styles . errorText } > Error loading image:</ Text >
124+ < Text style = { styles . errorMessage } > { error . message } </ Text >
125+ < Button title = "Try Again" onPress = { reset } />
126+ </ View >
127+ ) ;
128+ }
129+
76130export default function OutOfBandAssetsWithSuspenseExample ( ) {
77131 const [ uri , setUri ] = React . useState < ImageURLS > ( ImageURL1 ) ;
132+ const [ errorBoundaryKey , setErrorBoundaryKey ] = React . useState ( 0 ) ;
133+
134+ const handleReset = ( ) => {
135+ setErrorBoundaryKey ( ( k ) => k + 1 ) ;
136+ } ;
137+
138+ const renderErrorFallback = ( error : Error , reset : ( ) => void ) => (
139+ < ErrorFallback
140+ error = { error }
141+ reset = { ( ) => {
142+ reset ( ) ;
143+ handleReset ( ) ;
144+ } }
145+ />
146+ ) ;
78147
79148 return (
80149 < View style = { styles . safeAreaViewContainer } >
81- < React . Suspense
82- fallback = {
83- < View style = { styles . loadingContainer } >
84- < ActivityIndicator size = "large" />
85- < Text style = { styles . loadingText } > Loading image...</ Text >
86- </ View >
87- }
88- >
89- < RiveContent imageUrl = { uri } />
90- </ React . Suspense >
150+ < ErrorBoundary key = { errorBoundaryKey } fallback = { renderErrorFallback } >
151+ < React . Suspense
152+ fallback = {
153+ < View style = { styles . loadingContainer } >
154+ < ActivityIndicator size = "large" />
155+ < Text style = { styles . loadingText } > Loading image...</ Text >
156+ </ View >
157+ }
158+ >
159+ < RiveContent imageUrl = { uri } />
160+ </ React . Suspense >
161+ </ ErrorBoundary >
91162
92163 < ScrollView contentContainerStyle = { styles . container } >
93164 < View style = { styles . pickersWrapper } >
@@ -102,9 +173,11 @@ export default function OutOfBandAssetsWithSuspenseExample() {
102173 mode = { 'dropdown' }
103174 style = { styles . picker }
104175 >
105- { [ ImageURL1 , ImageURL2 , ImageURLSlow ] . map ( ( key ) => (
106- < Picker . Item key = { key } value = { key } label = { key } />
107- ) ) }
176+ { [ ImageURL1 , ImageURL2 , ImageURLSlow , ImageInvalidURL ] . map (
177+ ( url ) => (
178+ < Picker . Item key = { url } value = { url } label = { url } />
179+ )
180+ ) }
108181 </ Picker >
109182 </ View >
110183 </ View >
@@ -159,6 +232,26 @@ const styles = StyleSheet.create({
159232 marginBottom : 8 ,
160233 paddingHorizontal : 16 ,
161234 } ,
235+ errorContainer : {
236+ width : '100%' ,
237+ height : 400 ,
238+ justifyContent : 'center' ,
239+ alignItems : 'center' ,
240+ padding : 16 ,
241+ backgroundColor : '#ffebee' ,
242+ } ,
243+ errorText : {
244+ fontSize : 18 ,
245+ fontWeight : 'bold' ,
246+ color : '#c62828' ,
247+ marginBottom : 8 ,
248+ } ,
249+ errorMessage : {
250+ fontSize : 14 ,
251+ color : '#d32f2f' ,
252+ marginBottom : 16 ,
253+ textAlign : 'center' ,
254+ } ,
162255} ) ;
163256
164257OutOfBandAssetsWithSuspenseExample . metadata = {
0 commit comments