forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DX: display highlited pesudo html when bad nesting html error occurred (
vercel#62590) ### What When you have bad nesting html in your React code, React wil raise hydration error since the browser html parser might parse it to sth else different comparing to server html on client. Previously we're display only the warning in the error description in dev overlay. Now we introduced another format of displaying pesudo html that representing your code, with highlighting the html tag that causes the error. Since React might gives you the whole component stack of React tree, so we also introduced a way that can collapse the error. #### Example https://github.com/vercel/next.js/assets/4800338/622122d6-4d2e-4c8e-95e8-4864343e478b ### Why The reason we added this is that even we show the html diff, it could super large due to React ordering the html on client, so the mismatch might be a lot. The idea here is similar to what you saw when you passed down a bad event handler into server component, we displayed a pesudo html as it could hit your mind faster than just seeing the warning. The best way is to display the source code, but before we can show the source, getting component stack display as pseudo html instead of here could be more helpful. ### After vs Before <img width="400" src="https://github.com/vercel/next.js/assets/4800338/714119ad-ff23-46a9-bc5a-5601eb390e71"> <img width="400" src="https://github.com/vercel/next.js/assets/4800338/575f95fa-889e-4cee-ad19-9c2fea06519a"> Closes NEXT-2621
- Loading branch information
Showing
16 changed files
with
496 additions
and
146 deletions.
There are no files selected for viewing
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
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
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
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
133 changes: 133 additions & 0 deletions
133
...ponents/react-dev-overlay/internal/container/RuntimeError/component-stack-pseudo-html.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,133 @@ | ||
import { useMemo, Fragment, useState } from 'react' | ||
import type { ComponentStackFrame } from '../../helpers/parse-component-stack' | ||
import { CollapseIcon } from './GroupedStackFrames' | ||
|
||
const MAX_NON_COLLAPSED_FRAMES = 6 | ||
|
||
/** | ||
* | ||
* Format component stack into pseudo HTML | ||
* component stack is an array of strings, e.g.: ['p', 'p', 'Page', ...] | ||
* | ||
* Will render it for the code block | ||
* | ||
* <pre> | ||
* <code>{` | ||
* <Page> | ||
* <p> | ||
* ^^^^ | ||
* <p> | ||
* ^^^^ | ||
* `}</code> | ||
* </pre> | ||
* | ||
*/ | ||
export function PseudoHtml({ | ||
componentStackFrames, | ||
serverTagName, | ||
clientTagName, | ||
...props | ||
}: { | ||
componentStackFrames: ComponentStackFrame[] | ||
serverTagName?: string | ||
clientTagName?: string | ||
[prop: string]: any | ||
}) { | ||
const isHtmlTagsWarning = serverTagName || clientTagName | ||
const shouldCollapse = componentStackFrames.length > MAX_NON_COLLAPSED_FRAMES | ||
const [isHtmlCollapsed, toggleCollapseHtml] = useState(shouldCollapse) | ||
|
||
const htmlComponents = useMemo(() => { | ||
const tagNames = [serverTagName, clientTagName] | ||
const nestedHtmlStack: React.ReactNode[] = [] | ||
let lastText = '' | ||
componentStackFrames | ||
.map((frame) => frame.component) | ||
.reverse() | ||
.forEach((component, index, componentList) => { | ||
const spaces = ' '.repeat(nestedHtmlStack.length * 2) | ||
const prevComponent = componentList[index - 1] | ||
const nextComponent = componentList[index + 1] | ||
// When component is the server or client tag name, highlight it | ||
|
||
const isHighlightedTag = tagNames.includes(component) | ||
const isRelatedTag = | ||
isHighlightedTag || | ||
tagNames.includes(prevComponent) || | ||
tagNames.includes(nextComponent) | ||
|
||
if ( | ||
nestedHtmlStack.length >= MAX_NON_COLLAPSED_FRAMES && | ||
isHtmlCollapsed | ||
) { | ||
return | ||
} | ||
if (isRelatedTag) { | ||
const TextWrap = isHighlightedTag ? 'b' : Fragment | ||
const codeLine = ( | ||
<span> | ||
<span>{spaces}</span> | ||
<TextWrap> | ||
{'<'} | ||
{component} | ||
{'>'} | ||
{'\n'} | ||
</TextWrap> | ||
</span> | ||
) | ||
lastText = component | ||
|
||
const wrappedCodeLine = ( | ||
<Fragment key={nestedHtmlStack.length}> | ||
{codeLine} | ||
{/* Add ^^^^ to the target tags */} | ||
{isHighlightedTag && ( | ||
<span>{spaces + '^'.repeat(component.length + 2) + '\n'}</span> | ||
)} | ||
</Fragment> | ||
) | ||
nestedHtmlStack.push(wrappedCodeLine) | ||
} else { | ||
if (!isHtmlCollapsed || !isHtmlTagsWarning) { | ||
nestedHtmlStack.push( | ||
<span key={nestedHtmlStack.length}> | ||
{spaces} | ||
{'<' + component + '>\n'} | ||
</span> | ||
) | ||
} else if (lastText !== '...') { | ||
lastText = '...' | ||
nestedHtmlStack.push( | ||
<span key={nestedHtmlStack.length}> | ||
{spaces} | ||
{'...'} | ||
{'\n'} | ||
</span> | ||
) | ||
} | ||
} | ||
}) | ||
|
||
return nestedHtmlStack | ||
}, [ | ||
componentStackFrames, | ||
isHtmlCollapsed, | ||
clientTagName, | ||
serverTagName, | ||
isHtmlTagsWarning, | ||
]) | ||
|
||
return ( | ||
<div data-nextjs-container-errors-pseudo-html> | ||
<span | ||
data-nextjs-container-errors-pseudo-html-collapse | ||
onClick={() => toggleCollapseHtml(!isHtmlCollapsed)} | ||
> | ||
<CollapseIcon collapsed={isHtmlCollapsed} /> | ||
</span> | ||
<pre {...props}> | ||
<code>{htmlComponents}</code> | ||
</pre> | ||
</div> | ||
) | ||
} |
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.