Add error boundary to nake frontend more resiliant

Allows users to recover from errors in userStorage

Is related to #23
This commit is contained in:
Thomas Nordquist
2019-01-18 13:13:36 +01:00
parent f462e7a881
commit 353081175b
3 changed files with 123 additions and 15 deletions

View File

@@ -12,6 +12,7 @@ import TitleBar from './components/TitleBar'
import Tree from './components/Tree/Tree' import Tree from './components/Tree/Tree'
import UpdateNotifier from './UpdateNotifier' import UpdateNotifier from './UpdateNotifier'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import ErrorBoundary from './ErrorBoundary'
interface State { interface State {
selectedNode?: q.TreeNode, selectedNode?: q.TreeNode,
@@ -82,9 +83,11 @@ class App extends React.Component<Props, State> {
public render() { public render() {
const { settingsVisible } = this.props const { settingsVisible } = this.props
const { content, contentShift, centerContent } = this.getStyles() const { content, contentShift, centerContent } = this.getStyles()
return ( return (
<div style={centerContent}> <div style={centerContent}>
<CssBaseline /> <CssBaseline />
<ErrorBoundary>
<Settings /> <Settings />
<div style={settingsVisible ? contentShift : content}> <div style={settingsVisible ? contentShift : content}>
<TitleBar /> <TitleBar />
@@ -101,6 +104,7 @@ class App extends React.Component<Props, State> {
</div> </div>
<UpdateNotifier /> <UpdateNotifier />
<Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/> <Connection onConnection={(connectionId: string) => this.setState({ connectionId })}/>
</ErrorBoundary>
</div > </div >
) )
} }

103
app/src/ErrorBoundary.tsx Normal file
View File

@@ -0,0 +1,103 @@
import * as React from 'react'
import * as q from '../../backend/src/Model'
import {
Button,
Modal,
Paper,
Toolbar,
Typography,
} from '@material-ui/core'
import Warning from '@material-ui/icons/Warning'
import SentimentDissatisfied from '@material-ui/icons/SentimentDissatisfied'
import { Theme, withStyles } from '@material-ui/core/styles'
interface State {
error?: Error
}
interface Props {
classes: any
}
class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {}
}
public componentDidCatch(error: Error, errorInfo: any) {
console.log('did catch', error)
}
public static getDerivedStateFromError(error: Error) {
return { error }
}
private restart = () => {
window.location = window.location
}
private clearStorage = () => {
localStorage.clear()
window.location = window.location
}
public render() {
if (!this.state.error) {
return this.props.children
}
const { classes } = this.props
return (
<Modal open={true} disableAutoFocus={true}>
<Paper className={classes.root}>
<Toolbar style={{ padding: '0' }}>
<Typography className={classes.title} variant="h6" color="inherit"><Warning /> Oooooops!</Typography>
</Toolbar>
<Typography>I hoped that you would never see this window, but MQTT-Explorer had an unexpected error.</Typography>
<Typography style={{ textAlign: 'center' }}><SentimentDissatisfied /></Typography>
<pre className={classes.textColor} style={{ maxHeight: '40vh', overflow: 'scroll' }}>
<code className={classes.textColor}>
{this.state.error.stack}
</code>
</pre>
<Typography>
Please report this issue with a short description of what happened to
<span> <a className={classes.textColor} href="https://github.com/thomasnordquist/MQTT-Explorer/issues">https://github.com/thomasnordquist/MQTT-Explorer/issues</a></span>
</Typography>
<div>
<div className={classes.buttonPositioning}><Button onClick={this.restart}>Restart</Button></div>
<div className={classes.buttonPositioning}><Button onClick={this.clearStorage}>Start Fresh</Button></div>
</div>
</Paper>
</Modal>
)
}
}
const styles = (theme: Theme) => ({
root: {
minWidth: 550,
maxWidth: 650,
backgroundColor: theme.palette.background.default,
margin: '14vh auto auto auto',
padding: `${2 * theme.spacing.unit}px`,
outline: 'none',
},
title: {
color: theme.palette.text.primary,
margin: '0',
textAlign: 'center' as 'center',
},
textColor: {
color: theme.palette.text.primary,
},
buttonPositioning: {
textAlign: 'center' as 'center',
marginTop: `${theme.spacing.unit * 2}px`,
},
})
export default withStyles(styles)(ErrorBoundary)

View File

@@ -1,3 +1,4 @@
import { rendererEvents, trackError, trackUserInteraction } from '../../events'
let userId = window.localStorage.getItem('userId') let userId = window.localStorage.getItem('userId')
const sha1 = require('sha1') const sha1 = require('sha1')