Error Boundary Addition (#297)

Error Boundary addition - if _Jellify_ encounters a fatal error - users will be given the ability to reload the app

---------

Co-authored-by: Ritesh Shukla <ritesh.shukla2@M-LD4JMWLW26.local>
Co-authored-by: Violet Caulfield <42452695+anultravioletaurora@users.noreply.github.com>
This commit is contained in:
Ritesh Shukla
2025-04-28 18:52:49 +05:30
committed by GitHub
parent ec3d37d22a
commit 6b0706d730
2 changed files with 139 additions and 22 deletions
+28 -22
View File
@@ -16,6 +16,7 @@ import { SafeAreaProvider } from 'react-native-safe-area-context'
import { NavigationContainer } from '@react-navigation/native'
import { JellifyDarkTheme, JellifyLightTheme } from './components/theme'
import { requestStoragePermission } from './helpers/permisson-helpers'
import ErrorBoundary from './components/ErrorBoundary'
export const backgroundRuntime = createWorkletRuntime('background')
@@ -54,32 +55,37 @@ export default function App(): React.JSX.Element {
console.log('playerIsReady', track)
}
getActiveTrack()
const [reloader, setReloader] = useState(0)
const handleRetry = () => setReloader((r) => r + 1)
return (
<SafeAreaProvider>
<NavigationContainer theme={isDarkMode ? JellifyDarkTheme : JellifyLightTheme}>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{
persister: clientPersister,
<ErrorBoundary reloader={reloader} onRetry={handleRetry}>
<NavigationContainer theme={isDarkMode ? JellifyDarkTheme : JellifyLightTheme}>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{
persister: clientPersister,
/**
* Infinity, since data can remain the
* same forever on the server
*/
maxAge: Infinity,
buster: '0.10.99',
}}
>
<GestureHandlerRootView>
<TamaguiProvider config={jellifyConfig}>
<Theme name={isDarkMode ? 'dark' : 'light'}>
{playerIsReady && <Jellify />}
</Theme>
</TamaguiProvider>
</GestureHandlerRootView>
</PersistQueryClientProvider>
</NavigationContainer>
/**
* Infinity, since data can remain the
* same forever on the server
*/
maxAge: Infinity,
buster: '0.10.99',
}}
>
<GestureHandlerRootView>
<TamaguiProvider config={jellifyConfig}>
<Theme name={isDarkMode ? 'dark' : 'light'}>
{playerIsReady && <Jellify />}
</Theme>
</TamaguiProvider>
</GestureHandlerRootView>
</PersistQueryClientProvider>
</NavigationContainer>
</ErrorBoundary>
</SafeAreaProvider>
)
}
+111
View File
@@ -0,0 +1,111 @@
import React from 'react'
import { View, Text, StyleSheet, TouchableOpacity, Dimensions } from 'react-native'
type Props = {
reloader: number
onRetry: () => void
onError?: (error: Error, info: React.ErrorInfo) => void
children: React.ReactNode
}
type State = {
hasError: boolean
error: Error | null
}
export default class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, info: React.ErrorInfo) {
if (__DEV__) return
this.setState({ hasError: true, error })
}
componentDidUpdate(prevProps: Props) {
// Reset error state if reloader prop changes
if (this.props.reloader !== prevProps.reloader) {
this.setState({ hasError: false, error: null })
}
}
handleRetry = () => {
if (this.props.onRetry) this.props.onRetry()
// Parent should increment reloader to trigger reset
}
render() {
if (this.state.hasError) {
console.log('this.state.hasError', this.state.hasError)
return (
<View style={styles.container}>
<Text style={styles.emoji}>🎵😵💫</Text>
<Text style={styles.title}>Oops! That was a wrong note.</Text>
<Text style={styles.subtitle}>
Jellify stopped unexpectedly. Lets tune things up and try again!
</Text>
<TouchableOpacity style={styles.button} onPress={this.handleRetry}>
<Text style={styles.buttonText}>Retry</Text>
</TouchableOpacity>
{/* {__DEV__ && this.state.error && (
<Text style={styles.devError}>{this.state.error.toString()}</Text>
)} */}
</View>
)
}
return this.props.children
}
}
const { width, height } = Dimensions.get('window')
const styles = StyleSheet.create({
container: {
flex: 1,
width,
height,
backgroundColor: '#18181b',
alignItems: 'center',
justifyContent: 'center',
padding: 32,
},
emoji: { fontSize: 60, marginBottom: 16 },
title: {
color: '#fff',
fontSize: 24,
fontWeight: 'bold',
marginBottom: 12,
textAlign: 'center',
},
subtitle: {
color: '#a1a1aa',
fontSize: 16,
marginBottom: 32,
textAlign: 'center',
lineHeight: 22,
},
button: {
backgroundColor: '#2563eb',
borderRadius: 25,
paddingVertical: 12,
paddingHorizontal: 32,
marginTop: 8,
},
buttonText: {
color: '#fff',
fontSize: 17,
fontWeight: '600',
letterSpacing: 1,
},
devError: {
marginTop: 24,
color: '#f87171',
fontSize: 12,
textAlign: 'center',
},
})