Provides a method to handle react component and ajax errors.
# for yarn
yarn add @clearc2/c2-error
# for npm
npm install @clearc2/c2-error
Configure the package by:
- setting the debug mode
- returning any app related information at the time of error
- supplying how errors should be reported(if at all)
- add the ajax error interceptor
// src/c2-error-config.js
import {addErrorInterceptor, setConfig} from '@clearc2/c2-error'
import ajax from '../ajax'
import store from '../store'
setConfig({
debug: ['staging', 'development'].includes(global.NODE_ENV),
getInfo: () => {
return {
url: window.location.href,
date: new Date(),
loginId: store.getState().getIn(['Users', 'currentLoginId']),
env: global.NODE_ENV
}
},
reportAjaxError: (props) => {
const {closeToast, ...error} = props
return ajax.post('/api-error', {error}).then(closeToast)
},
reportComponentError: (props) => {
const {closeToast, ...error} = props
return new Promise(resolve => {
window.location.href =
`mailto:${global.errorEmail}` +
`&subject=Runtime Error <project-name>` +
`&body=${JSON.stringify(error, null, 4)}`
closeToast()
resolve()
})
}
})
addErrorInterceptor(ajax)c2-error uses react-toastify to display error notifications. To display
toasts, a <ToastContainer /> needs to be added to a root component that is always rendered regardless of route.
import {ToastContainer} from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'
function App () {
return (
<div className='main-content'>
<ToastContainer />
{/* other app stuff */}
</div>
)
}All fields are optional.
Determines whether or not to display debug information in the toast. Defaults to false.
This should return any arbitrary information collected at the time the error occurs. Defaults to function that returns
{url: window.location.href, date: new Date()}.
Default toast options. Defaults to {autoClose: false, closeOnClick: false}.
Must return a Promise. This function gets an object with the following keys: message, closeToast, info, error.
closeToast is the function to programtically close the toast. error is the axios error object.
Must return a Promise. This function gets an object with the following keys: message, closeToast, info, error,
errorInfo, componentProps.
error and errorInfo are what the error boundary catches.
Read about these objects here. componentProps
are the props that the underlying component received.
c2-error can handle two types of errors: component and ajax.
Use the onCatch higher-order component to provide a placeholder and toast message.
import React from 'react'
import {onCatch} from '@clearc2/c2-error'
function UserTable () {
undefined.test() // intentional error
return (
<span>UserTable</span>
)
}
export default onCatch({
placeholder: 'Error :(',
message: 'User table error'
})(UserTable)
// or customize the toast
export default onCatch({
placeholder: 'Error :(',
message: 'User table error',
type: 'warn',
options: {autoClose: true}
})(UserTable)
// or customize the message based on props or the error
export default onCatch({
placeholder: 'Error :(',
message: (props, error, errorInfo) => `User table error. User: ${props.loginId}`
})(UserTable)The onCatch wraps the component in an error boundary. It does two
things.
- Displays the placeholder instead of the component if it errors(
UserTablein the example above) - Creates a toast
Use the ifErrorsProp HOC to display a placeholder if the component receives an errors prop.
import {onCatch, ifErrorsProp} from '@clearc2/c2-error'
const enhance = compose(
onCatch({placeholder: 'Error', message: 'User table error.'}),
connect(userTableSelector, {fetchUsers}),
ifErrorsProp({placeholder: 'Error'})
)
export default enhance(UserTable)The onCatch HOC is meant to catch thrown javascript runtime errors using a React error boundary. The ifErrorsProp HOC displays a placeholder if errors are passed as a prop. For example, your component's redux selector can map in errors from a request(s).
The ifErrorsProp HOC is not meant to be used if the component is expecting errors to be passed to it. For example, with a form, it is very likely that users will enter invalid data and the api will respond with validation errors. Rather than replacing the whole form with a placeholder, the form will most likely want to render those errors above the inputs to allow the user to correct their input.
c2-error uses axios's config object to display errors if
the request promise is rejected.
// use a simple error message
axios.get('/sites/123/tickets', {onError: `Error fetching site 123's tickets.`})
// or return a string based on the axios error
axios.get('/sites/123/tickets', {
onError: (error) => {
if (error.response.status === 404) {
return `Couldn't find site 123's tickets.`
}
return `Error fetching site 123's tickets.`
}
})
// or customize the toast by returning an object
axios.get('/sites/123/tickets', {
onError: (error) => {
return {
message: `${error.response.status} - Error fetching site 123's tickets.`,
type: 'warn',
options: {
autoClose: true
}
}
}
})Higher-order components can become awkward if several are used on the same component. Redux comes with a compose
function that makes this less awkward.
import React from 'react'
import {compose} from 'redux'
import {connect} from 'react-redux'
import {onCatch, ifErrorsProp} from '@clearc2/c2-error'
import SitesTable from './SitesTable'
import selector from './selector'
import {fetchSites} from '../actions'
const enhance = compose(
onCatch({placeholder: <BrokenIcon />, message: 'Sites table error'}),
connect(selector, {fetchSites}),
ifErrorsProp({placeholder: <BrokenIcon />})
)
export default enhance(SitesTable)
// the above is the same as this:
export default onCatch({placeholder: <BrokenIcon />, message: 'Sites table error'})(connect(selector, {fetchSites})(ifErrorsProp({placeholder: <BrokenIcon />})(UserTable)))