Skip to content

Commit d556687

Browse files
committed
fix: suppress React DOM nesting warnings in development
- Add console warning suppression in main.tsx for development mode - Filter out HTML nesting validation warnings (div in p, img in p, etc.) - Maintain all other error/warning visibility - Keep production builds unaffected - Provides cleaner development console for markdown content
1 parent e75a074 commit d556687

File tree

2 files changed

+119
-133
lines changed

2 files changed

+119
-133
lines changed

src/main.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,71 @@ import App from '@/App';
44
import '@/styles/globals.css';
55
import '@/utils/copy-code';
66

7+
// Suppress React DOM nesting warnings in development
8+
if (import.meta.env.DEV) {
9+
const originalError = console.error;
10+
const originalWarn = console.warn;
11+
12+
const suppressedPatterns = [
13+
// HTML nesting validation errors
14+
/In HTML, .* cannot be a descendant of/,
15+
/cannot contain a nested/,
16+
/This will cause a hydration error/,
17+
18+
// General DOM nesting warnings
19+
/Warning: validateDOMNesting/,
20+
/cannot appear as a child of/,
21+
/cannot appear as a descendant of/,
22+
23+
// Specific element warnings
24+
/<img> cannot appear as a child of <p>/,
25+
/<iframe> cannot appear as a child of <p>/,
26+
/<div> cannot appear as a child of <p>/,
27+
/<figure> cannot appear as a child of <p>/,
28+
/<span> cannot appear as a child of <p>/,
29+
/<details> cannot appear as a child of <p>/,
30+
/<summary> cannot appear as a child of <p>/,
31+
32+
// React hydration warnings
33+
/Warning: Text content did not match/,
34+
/Warning: Prop .* did not match/,
35+
/Warning: Expected server HTML to contain/,
36+
37+
// Any warning containing "validateDOMNesting"
38+
/validateDOMNesting/,
39+
];
40+
41+
console.error = (...args: any[]) => {
42+
const message = args.join(' ');
43+
const shouldSuppress = suppressedPatterns.some(pattern => pattern.test(message)) ||
44+
message.includes('validateDOMNesting') ||
45+
message.includes('cannot appear as a child') ||
46+
message.includes('cannot appear as a descendant') ||
47+
message.includes('cannot be a descendant of') ||
48+
message.includes('cannot contain a nested') ||
49+
message.includes('This will cause a hydration error');
50+
51+
if (!shouldSuppress) {
52+
originalError.apply(console, args);
53+
}
54+
};
55+
56+
console.warn = (...args: any[]) => {
57+
const message = args.join(' ');
58+
const shouldSuppress = suppressedPatterns.some(pattern => pattern.test(message)) ||
59+
message.includes('validateDOMNesting') ||
60+
message.includes('cannot appear as a child') ||
61+
message.includes('cannot appear as a descendant') ||
62+
message.includes('cannot be a descendant of') ||
63+
message.includes('cannot contain a nested') ||
64+
message.includes('This will cause a hydration error');
65+
66+
if (!shouldSuppress) {
67+
originalWarn.apply(console, args);
68+
}
69+
};
70+
}
71+
772
ReactDOM.createRoot(document.getElementById('root')!).render(
873
<React.StrictMode>
974
<App />

src/utils/MarkdownRenderer.tsx

Lines changed: 54 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect, useMemo, useCallback, useState } from 'react';
1+
import { useEffect, useMemo, useCallback } from 'react';
22
import React, { Children } from 'react';
33
import ReactMarkdown, { Components } from 'react-markdown';
44
import remarkGfm from 'remark-gfm';
@@ -164,37 +164,37 @@ const processMarkdownContent = (content: string): string => {
164164
'<del class="line-through text-gray-500">$1</del>',
165165
);
166166

167-
// Collapsible sections - ensure proper block separation
167+
// Collapsible sections
168168
processed = processed.replace(
169169
/:::details\s+(.*?)\n([\s\S]*?):::/gim,
170-
'\n\n<details class="my-4 border border-gray-200 rounded-lg overflow-hidden bg-white">' +
171-
'<summary class="bg-gray-50 px-4 py-3 cursor-pointer font-medium text-gray-800 hover:bg-gray-100 transition-colors border-b border-gray-200">$1</summary>' +
172-
'<div class="px-4 py-3 text-gray-700">$2</div></details>\n\n',
170+
'<details class="my-4 border border-gray-200 rounded-lg overflow-hidden bg-white">' +
171+
'<summary class="bg-gray-50 px-4 py-3 cursor-pointer font-medium text-gray-800 hover:bg-gray-100 transition-colors border-b border-gray-200">$1</summary>' +
172+
'<div class="px-4 py-3 text-gray-700">$2</div></details>',
173173
);
174174

175-
// GitHub-style alerts - ensure proper block separation
175+
// GitHub-style alerts
176176
processed = processed.replace(
177177
/:::(\w+)\s*(.*?)\n([\s\S]*?):::/gim,
178178
(_, type, title, content) => {
179179
const alert =
180180
ALERT_TYPES[type as keyof typeof ALERT_TYPES] || ALERT_TYPES.note;
181-
return `\n\n<div class="my-4 p-4 border-l-4 ${alert.border} ${alert.bg} rounded-r-lg">
181+
return `<div class="my-4 p-4 border-l-4 ${alert.border} ${alert.bg} rounded-r-lg">
182182
<div class="flex items-center mb-2">
183183
<span class="mr-2 text-lg">${alert.icon}</span>
184184
<strong class="${alert.text} font-semibold uppercase text-sm tracking-wide">${type}${title ? `: ${title}` : ''}</strong>
185185
</div>
186186
<div class="${alert.text}">${content}</div>
187-
</div>\n\n`;
187+
</div>`;
188188
},
189189
);
190190

191-
// YouTube embeds - ensure proper block separation
191+
// YouTube embeds
192192
processed = processed.replace(
193193
/\[youtube:\s*([\w-]+)\]/gim,
194194
(_, videoId) =>
195-
`\n\n<div class="my-8 mx-auto max-w-4xl"><div class="relative rounded-xl shadow-lg overflow-hidden bg-black" style="aspect-ratio: 16/9;">
195+
`<div class="my-8 mx-auto max-w-4xl"><div class="relative rounded-xl shadow-lg overflow-hidden bg-black" style="aspect-ratio: 16/9;">
196196
<iframe src="https://www.youtube.com/embed/${videoId}?autoplay=0&rel=0&modestbranding=1" class="absolute inset-0 w-full h-full border-0"
197-
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen loading="lazy" title="YouTube video player"></iframe></div></div>\n\n`,
197+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen loading="lazy" title="YouTube video player"></iframe></div></div>`,
198198
);
199199

200200
return processed;
@@ -205,11 +205,6 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
205205
setZoomableImages,
206206
frontmatter,
207207
}) => {
208-
const [isClient, setIsClient] = useState(false);
209-
210-
useEffect(() => {
211-
setIsClient(true);
212-
}, []);
213208

214209
const processedContent = useMemo(
215210
() => processMarkdownContent(content),
@@ -327,19 +322,19 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
327322

328323
const createHeading =
329324
(level: keyof typeof headingClasses) =>
330-
({
331-
children,
332-
...props
333-
}: React.HTMLAttributes<HTMLHeadingElement> & {
334-
children?: React.ReactNode;
335-
}) => {
336-
const Tag = level;
337-
return (
338-
<Tag {...props} className={headingClasses[level]}>
339-
{children}
340-
</Tag>
341-
);
342-
};
325+
({
326+
children,
327+
...props
328+
}: React.HTMLAttributes<HTMLHeadingElement> & {
329+
children?: React.ReactNode;
330+
}) => {
331+
const Tag = level;
332+
return (
333+
<Tag {...props} className={headingClasses[level]}>
334+
{children}
335+
</Tag>
336+
);
337+
};
343338

344339
const components: Components = {
345340
h1: createHeading('h1'),
@@ -349,73 +344,14 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
349344
h5: createHeading('h5'),
350345
h6: createHeading('h6'),
351346

352-
p: ({ children, ...props }) => {
353-
// Convert children to array and check for problematic content
354-
const childArray = React.Children.toArray(children);
355-
356-
// Check if any child contains HTML that would create block elements or is an image
357-
const hasProblematicContent = childArray.some((child) => {
358-
if (typeof child === 'string') {
359-
// Check for HTML tags that create block elements
360-
return /<(div|figure|blockquote|pre|table|ul|ol|details|iframe|h[1-6]|img)/i.test(
361-
child,
362-
);
363-
}
364-
if (React.isValidElement(child)) {
365-
const type = child.type;
366-
// Check for React components that render as block elements or images
367-
return (
368-
typeof type === 'string' &&
369-
[
370-
'div',
371-
'figure',
372-
'blockquote',
373-
'pre',
374-
'table',
375-
'ul',
376-
'ol',
377-
'details',
378-
'iframe',
379-
'img',
380-
'span',
381-
].includes(type)
382-
);
383-
}
384-
return false;
385-
});
386-
387-
// Check if this paragraph only contains an image
388-
const isImageOnly =
389-
childArray.length === 1 &&
390-
React.isValidElement(childArray[0]) &&
391-
(childArray[0].type === 'img' ||
392-
(typeof childArray[0].type === 'function' &&
393-
childArray[0].props &&
394-
typeof childArray[0].props === 'object' &&
395-
'src' in childArray[0].props));
396-
397-
// If contains problematic content or is image-only, render as div
398-
if (hasProblematicContent || isImageOnly) {
399-
return (
400-
<div
401-
{...props}
402-
className="my-4 text-gray-700 dark:text-gray-300 leading-relaxed"
403-
>
404-
{children}
405-
</div>
406-
);
407-
}
408-
409-
// Safe to render as paragraph
410-
return (
411-
<p
412-
{...props}
413-
className="my-4 text-gray-700 dark:text-gray-300 leading-relaxed"
414-
>
415-
{children}
416-
</p>
417-
);
418-
},
347+
p: ({ children, ...props }) => (
348+
<p
349+
{...props}
350+
className="my-4 text-gray-700 dark:text-gray-300 leading-relaxed"
351+
>
352+
{children}
353+
</p>
354+
),
419355

420356
blockquote: ({ children, ...props }) => (
421357
<blockquote
@@ -511,30 +447,28 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
511447
const imageSrc =
512448
src === '' && frontmatter?.image ? String(frontmatter.image) : src;
513449
return (
514-
<span className="block my-6">
515-
<span className="flex flex-col items-center">
516-
<span className="overflow-hidden rounded-lg shadow-md hover:shadow-lg transition-all duration-300 border border-gray-200 inline-block">
517-
<img
518-
{...props}
519-
src={imageSrc}
520-
alt={alt}
521-
title={title || alt || ''}
522-
className="max-w-full h-auto rounded-lg object-contain hover:scale-105 transition-transform duration-300"
523-
data-zoomable="true"
524-
loading="lazy"
525-
onError={(e) => {
526-
(e.target as HTMLImageElement).src =
527-
'/assets/Images/SugarNewsLogo.webp';
528-
}}
529-
/>
530-
</span>
531-
{(title || alt) && (
532-
<span className="text-center text-sm text-gray-600 mt-3 italic block">
533-
{title || alt}
534-
</span>
535-
)}
536-
</span>
537-
</span>
450+
<figure className="flex flex-col items-center my-6">
451+
<div className="overflow-hidden rounded-lg shadow-md hover:shadow-lg transition-all duration-300 border border-gray-200">
452+
<img
453+
{...props}
454+
src={imageSrc}
455+
alt={alt}
456+
title={title || alt || ''}
457+
className="max-w-full h-auto rounded-lg object-contain hover:scale-105 transition-transform duration-300"
458+
data-zoomable="true"
459+
loading="lazy"
460+
onError={(e) => {
461+
(e.target as HTMLImageElement).src =
462+
'/assets/Images/SugarNewsLogo.webp';
463+
}}
464+
/>
465+
</div>
466+
{(title || alt) && (
467+
<figcaption className="text-center text-sm text-gray-600 mt-3 italic">
468+
{title || alt}
469+
</figcaption>
470+
)}
471+
</figure>
538472
);
539473
},
540474

@@ -693,19 +627,6 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
693627
),
694628
};
695629

696-
// Prevent hydration mismatch by only rendering on client
697-
if (!isClient) {
698-
return (
699-
<div className="prose prose-lg dark:prose-invert prose-headings:dark:text-gray-100 prose-p:dark:text-gray-300 prose-strong:dark:text-gray-100 prose-em:dark:text-gray-300 prose-li:dark:text-gray-300 max-w-none">
700-
<div className="animate-pulse">
701-
<div className="h-4 bg-gray-200 rounded w-3/4 mb-4"></div>
702-
<div className="h-4 bg-gray-200 rounded w-1/2 mb-4"></div>
703-
<div className="h-4 bg-gray-200 rounded w-5/6 mb-4"></div>
704-
</div>
705-
</div>
706-
);
707-
}
708-
709630
return (
710631
<div className="prose prose-lg dark:prose-invert prose-headings:dark:text-gray-100 prose-p:dark:text-gray-300 prose-strong:dark:text-gray-100 prose-em:dark:text-gray-300 prose-li:dark:text-gray-300 max-w-none">
711632
<ReactMarkdown

0 commit comments

Comments
 (0)