-
Notifications
You must be signed in to change notification settings - Fork 27.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DevOverlay] Decouple Error Overlay with DevTools Indicator
- Loading branch information
1 parent
386a89b
commit db209da
Showing
13 changed files
with
589 additions
and
400 deletions.
There are no files selected for viewing
58 changes: 58 additions & 0 deletions
58
packages/next/src/client/components/react-dev-overlay/_experimental/app/error-boundary.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { PureComponent } from 'react' | ||
import { RuntimeErrorHandler } from '../internal/helpers/runtime-error-handler' | ||
|
||
type ReactDevOverlayProps = { | ||
children: React.ReactNode[] | ||
onError: (value: boolean) => void | ||
} | ||
|
||
type ReactDevOverlayState = { | ||
isReactError: boolean | ||
} | ||
|
||
export class ErrorBoundary extends PureComponent< | ||
ReactDevOverlayProps, | ||
ReactDevOverlayState | ||
> { | ||
state = { isReactError: false } | ||
|
||
componentDidUpdate( | ||
_prevProps: ReactDevOverlayProps, | ||
prevState: ReactDevOverlayState | ||
) { | ||
if (prevState.isReactError !== this.state.isReactError) { | ||
this.props.onError(this.state.isReactError) | ||
} | ||
} | ||
|
||
static getDerivedStateFromError(error: Error): ReactDevOverlayState { | ||
if (!error.stack) { | ||
return { isReactError: false } | ||
} | ||
|
||
RuntimeErrorHandler.hadRuntimeError = true | ||
|
||
return { | ||
isReactError: true, | ||
} | ||
} | ||
|
||
render() { | ||
const { children } = this.props | ||
const [content, devtools] = children | ||
|
||
const fallback = ( | ||
<html> | ||
<head></head> | ||
<body></body> | ||
</html> | ||
) | ||
|
||
return ( | ||
<> | ||
{this.state.isReactError ? fallback : content} | ||
{devtools} | ||
</> | ||
) | ||
} | ||
} |
85 changes: 85 additions & 0 deletions
85
...t/src/client/components/react-dev-overlay/_experimental/app/react-dev-overlay.stories.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import type { Meta, StoryObj } from '@storybook/react' | ||
import type { OverlayState } from '../../shared' | ||
|
||
import ReactDevOverlay from './react-dev-overlay' | ||
import { ACTION_UNHANDLED_ERROR } from '../../shared' | ||
|
||
const meta: Meta<typeof ReactDevOverlay> = { | ||
component: ReactDevOverlay, | ||
parameters: { | ||
layout: 'fullscreen', | ||
}, | ||
} | ||
|
||
export default meta | ||
type Story = StoryObj<typeof ReactDevOverlay> | ||
|
||
const state: OverlayState = { | ||
nextId: 0, | ||
buildError: null, | ||
errors: [ | ||
{ | ||
id: 1, | ||
event: { | ||
type: ACTION_UNHANDLED_ERROR, | ||
reason: Object.assign(new Error('First error message'), { | ||
__NEXT_ERROR_CODE: 'E001', | ||
}), | ||
componentStackFrames: [ | ||
{ | ||
file: 'app/page.tsx', | ||
component: 'Home', | ||
lineNumber: 10, | ||
column: 5, | ||
canOpenInEditor: true, | ||
}, | ||
], | ||
frames: [ | ||
{ | ||
file: 'app/page.tsx', | ||
methodName: 'Home', | ||
arguments: [], | ||
lineNumber: 10, | ||
column: 5, | ||
}, | ||
], | ||
}, | ||
}, | ||
{ | ||
id: 2, | ||
event: { | ||
type: ACTION_UNHANDLED_ERROR, | ||
reason: Object.assign(new Error('Second error message'), { | ||
__NEXT_ERROR_CODE: 'E002', | ||
}), | ||
frames: [], | ||
}, | ||
}, | ||
{ | ||
id: 3, | ||
event: { | ||
type: ACTION_UNHANDLED_ERROR, | ||
reason: Object.assign(new Error('Third error message'), { | ||
__NEXT_ERROR_CODE: 'E003', | ||
}), | ||
frames: [], | ||
}, | ||
}, | ||
], | ||
refreshState: { type: 'idle' }, | ||
rootLayoutMissingTags: [], | ||
notFound: false, | ||
staticIndicator: false, | ||
debugInfo: { devtoolsFrontendUrl: undefined }, | ||
versionInfo: { | ||
installed: '14.2.0', | ||
staleness: 'fresh', | ||
}, | ||
} | ||
|
||
export const Default: Story = { | ||
args: { | ||
state, | ||
children: <div>Application Content</div>, | ||
}, | ||
} |
123 changes: 41 additions & 82 deletions
123
...ages/next/src/client/components/react-dev-overlay/_experimental/app/react-dev-overlay.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,48 @@ | ||
import type { OverlayState } from '../../shared' | ||
import type { Dispatcher } from '../../app/hot-reloader-client' | ||
|
||
import React from 'react' | ||
|
||
import { useState } from 'react' | ||
import { ErrorBoundary } from './error-boundary' | ||
import { ShadowPortal } from '../internal/components/shadow-portal' | ||
import { BuildError } from '../internal/container/build-error' | ||
import { Errors } from '../internal/container/errors' | ||
import { Base } from '../internal/styles/base' | ||
import { ComponentStyles } from '../internal/styles/component-styles' | ||
import { CssReset } from '../internal/styles/css-reset' | ||
import { RootLayoutMissingTagsError } from '../internal/container/root-layout-missing-tags-error' | ||
import { RuntimeErrorHandler } from '../internal/helpers/runtime-error-handler' | ||
import { Colors } from '../internal/styles/colors' | ||
|
||
interface ReactDevOverlayState { | ||
isReactError: boolean | ||
} | ||
export default class ReactDevOverlay extends React.PureComponent< | ||
{ | ||
state: OverlayState | ||
dispatcher?: Dispatcher | ||
children: React.ReactNode | ||
}, | ||
ReactDevOverlayState | ||
> { | ||
state = { isReactError: false } | ||
|
||
static getDerivedStateFromError(error: Error): ReactDevOverlayState { | ||
if (!error.stack) return { isReactError: false } | ||
|
||
RuntimeErrorHandler.hadRuntimeError = true | ||
return { | ||
isReactError: true, | ||
} | ||
} | ||
|
||
render() { | ||
const { state, children } = this.props | ||
const { isReactError } = this.state | ||
|
||
const hasBuildError = state.buildError != null | ||
const hasStaticIndicator = state.staticIndicator | ||
const debugInfo = state.debugInfo | ||
|
||
const isTurbopack = !!process.env.TURBOPACK | ||
|
||
return ( | ||
<> | ||
{isReactError ? ( | ||
<html> | ||
<head></head> | ||
<body></body> | ||
</html> | ||
) : ( | ||
children | ||
)} | ||
<ShadowPortal> | ||
<CssReset /> | ||
<Base /> | ||
<Colors /> | ||
<ComponentStyles /> | ||
{state.rootLayoutMissingTags?.length ? ( | ||
<RootLayoutMissingTagsError | ||
missingTags={state.rootLayoutMissingTags} | ||
isTurbopack={isTurbopack} | ||
/> | ||
) : hasBuildError ? ( | ||
<BuildError | ||
message={state.buildError!} | ||
versionInfo={state.versionInfo} | ||
isTurbopack={isTurbopack} | ||
/> | ||
) : ( | ||
<Errors | ||
isTurbopack={isTurbopack} | ||
isAppDir={true} | ||
initialDisplayState={isReactError ? 'fullscreen' : 'minimized'} | ||
errors={state.errors} | ||
versionInfo={state.versionInfo} | ||
hasStaticIndicator={hasStaticIndicator} | ||
debugInfo={debugInfo} | ||
/> | ||
)} | ||
</ShadowPortal> | ||
</> | ||
) | ||
} | ||
import { ErrorOverlay } from '../internal/components/errors/error-overlay/error-overlay' | ||
import { DevToolsIndicator } from '../internal/components/errors/dev-tools-indicator/dev-tools-indicator' | ||
import { useErrorHook } from '../internal/container/runtime-error/use-error-hook' | ||
|
||
export default function ReactDevOverlay({ | ||
state, | ||
children, | ||
}: { | ||
state: OverlayState | ||
children: React.ReactNode | ||
}) { | ||
const [isErrorOverlayOpen, setIsErrorOverlayOpen] = useState(false) | ||
const { readyErrors } = useErrorHook({ errors: state.errors, isAppDir: true }) | ||
|
||
return ( | ||
<ErrorBoundary onError={setIsErrorOverlayOpen}> | ||
{children} | ||
|
||
<ShadowPortal> | ||
<CssReset /> | ||
<Base /> | ||
<Colors /> | ||
<ComponentStyles /> | ||
|
||
<DevToolsIndicator | ||
state={state} | ||
readyErrorsLength={readyErrors.length} | ||
setIsErrorOverlayOpen={setIsErrorOverlayOpen} | ||
/> | ||
|
||
<ErrorOverlay | ||
state={state} | ||
readyErrors={readyErrors} | ||
isErrorOverlayOpen={isErrorOverlayOpen} | ||
setIsErrorOverlayOpen={setIsErrorOverlayOpen} | ||
/> | ||
</ShadowPortal> | ||
</ErrorBoundary> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.