Skip to content

Commit

Permalink
feat(react-dev-overlay): Report full parsed runtime errors over Bus +…
Browse files Browse the repository at this point in the history
… accepts preventDisplay prop to avoid showing error messages inline, while still reporting errors over the bus
  • Loading branch information
natew committed Feb 11, 2022
1 parent 2e0598d commit 89df91e
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 3 deletions.
8 changes: 7 additions & 1 deletion packages/react-dev-overlay/src/internal/ReactDevOverlay.tsx
Expand Up @@ -50,6 +50,8 @@ function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState {
errors: [...state.errors, { id: state.nextId, event: ev }],
}
}
case Bus.TYPE_UNHANDLED_ERROR_FULL:
return { ...state }
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _: never = ev
Expand All @@ -60,6 +62,10 @@ function reducer(state: OverlayState, ev: Bus.BusEvent): OverlayState {

const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({
children,
preventDisplay,
}: {
children?: React.ReactNode
preventDisplay?: boolean
}) {
const [state, dispatch] = React.useReducer<
React.Reducer<OverlayState, Bus.BusEvent>
Expand Down Expand Up @@ -104,7 +110,7 @@ const ReactDevOverlay: React.FunctionComponent = function ReactDevOverlay({
{hasBuildError ? (
<BuildError message={state.buildError!} />
) : hasRuntimeErrors ? (
<Errors errors={state.errors} />
<Errors preventDisplay={preventDisplay} errors={state.errors} />
) : isAboutToFullRefresh ? (
<FullRefreshWarning reason={state.fullRefreshReason} />
) : undefined}
Expand Down
15 changes: 15 additions & 0 deletions packages/react-dev-overlay/src/internal/bus.ts
@@ -1,11 +1,13 @@
import { StackFrame } from 'stacktrace-parser'
import { OriginalStackFrame } from './helpers/stack-frame'

export const TYPE_BUILD_OK = 'build-ok'
export const TYPE_BUILD_ERROR = 'build-error'
export const TYPE_FULL_REFRESH_NEEDED = 'full-refresh-needed'
export const TYPE_REFRESH = 'fast-refresh'
export const TYPE_UNHANDLED_ERROR = 'unhandled-error'
export const TYPE_UNHANDLED_REJECTION = 'unhandled-rejection'
export const TYPE_UNHANDLED_ERROR_FULL = 'unhandled-error-full'

export type BuildOk = { type: typeof TYPE_BUILD_OK }
export type BuildError = {
Expand All @@ -22,6 +24,18 @@ export type UnhandledError = {
reason: Error
frames: StackFrame[]
}
export type UnhandledErrorFull = {
type: typeof TYPE_UNHANDLED_ERROR_FULL
error: {
name: Error['name']
message: Error['message']
stack?: Error['stack']
}
id: number
firstFrame: OriginalStackFrame
leadingFrames: any[]
visibleCallStackFrames: any[]
}
export type UnhandledRejection = {
type: typeof TYPE_UNHANDLED_REJECTION
reason: Error
Expand All @@ -34,6 +48,7 @@ export type BusEvent =
| FullRefreshNeeded
| UnhandledError
| UnhandledRejection
| UnhandledErrorFull

export type BusEventHandler = (ev: BusEvent) => void

Expand Down
19 changes: 17 additions & 2 deletions packages/react-dev-overlay/src/internal/container/Errors.tsx
Expand Up @@ -26,7 +26,10 @@ export type SupportedErrorEvent = {
id: number
event: UnhandledError | UnhandledRejection
}
export type ErrorsProps = { errors: SupportedErrorEvent[] }
export type ErrorsProps = {
errors: SupportedErrorEvent[]
preventDisplay?: boolean
}

export type ReadyRuntimeError = {
id: number
Expand Down Expand Up @@ -108,7 +111,10 @@ async function getErrorByType(
throw new Error('type system invariant violation')
}

export const Errors: React.FC<ErrorsProps> = function Errors({ errors }) {
export const Errors: React.FC<ErrorsProps> = function Errors({
errors,
preventDisplay,
}) {
const [lookups, setLookups] = React.useState(
{} as { [eventId: string]: ReadyErrorEvent }
)
Expand Down Expand Up @@ -226,6 +232,15 @@ export const Errors: React.FC<ErrorsProps> = function Errors({ errors }) {
return <Overlay />
}

if (preventDisplay) {
// so error processing still happens and can report
return (
<div style={{ display: 'none' }}>
<RuntimeError key={activeError.id.toString()} error={activeError} />
</div>
)
}

if (isMinimized) {
return (
<Toast className="nextjs-toast-errors-parent" onClick={reopen}>
Expand Down
23 changes: 23 additions & 0 deletions packages/react-dev-overlay/src/internal/container/RuntimeError.tsx
Expand Up @@ -5,6 +5,8 @@ import { noop as css } from '../helpers/noop-template'
import { getFrameSource, OriginalStackFrame } from '../helpers/stack-frame'
import { ReadyRuntimeError } from './Errors'

import * as Bus from '../bus'

export type RuntimeErrorProps = { error: ReadyRuntimeError }

const CallStackFrame: React.FC<{
Expand Down Expand Up @@ -122,6 +124,27 @@ const RuntimeError: React.FC<RuntimeErrorProps> = function RuntimeError({
visibleCallStackFrames.length,
])

React.useEffect(
() => {
if (firstFrame) {
Bus.emit({
type: Bus.TYPE_UNHANDLED_ERROR_FULL,
error: {
name: error.error.name,
message: error.error.message,
stack: error.error.stack,
},
id: error.id,
firstFrame,
leadingFrames,
visibleCallStackFrames,
})
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[firstFrame]
)

return (
<React.Fragment>
{firstFrame ? (
Expand Down

0 comments on commit 89df91e

Please sign in to comment.