Debugging React Layout Issues
React components encapsulate markup, styles, and logic. When a layout breaks, the cause might be in the component's CSS, its parent component's CSS, or the interaction between them. Debugging requires understanding both CSS behavior and React's rendering model.
Problem
Layout issues in React applications manifest as:
- Components rendering with unexpected sizes
- Styling from one component affecting another
- Layout shifts during re-renders
- Styles not applying despite correct class names
These problems are harder to diagnose because CSS comes from multiple sources: component styles, global styles, CSS-in-JS, and third-party libraries.
Why It Happens
CSS Specificity Conflicts
React apps often combine:
- Global CSS resets
- Component CSS modules
- Third-party component libraries
- Inline styles via style prop
- CSS-in-JS libraries
Specificity battles become common:
/* Global reset */
* {
margin: 0;
box-sizing: border-box;
}
/* Component library */
.button {
padding: 12px 24px !important; /* Library uses !important */
}
/* Your styles */
.my-button {
padding: 8px 16px; /* Does not apply */
}
Render Timing Issues
Layout measurements made during render may be incorrect:
function Component() {
const ref = useRef<HTMLDivElement>(null);
// This runs during render, before paint
const width = ref.current?.offsetWidth; // undefined on first render
return <div ref={ref}>Content</div>;
}
CSS-in-JS Ordering
CSS-in-JS libraries inject styles dynamically. if injection order is wrong, styles may not cascade correctly:
// These may inject in unpredictable order
const RedText = styled.span`color: red;`;
const BlueText = styled(RedText)`color: blue;`; // Should override
Solution
Use systematic debugging techniques specific to React applications.
Implementation
DevTools Component Tab:
React DevTools shows:
- Component hierarchy
- Props passed to each component
- State values
- Rendered output
Check if expected props are reaching child components and verify style-related props are correct.
Isolate Components:
Render the problematic component in isolation:
// Create a debug page
function DebugPage() {
return (
<div style={{ padding: '100px' }}>
<ProblematicComponent
// Pass exact props causing issues
item={testItem}
isActive={true}
/>
</div>
);
}
If the issue disappears, the problem is in the parent context.
Check for Portal Issues:
Portaled components (modals, tooltips, dropdowns) render outside their parent hierarchy but may still receive broken styles:
// Modal renders at document.body
createPortal(
<div className="modal">{children}</div>,
document.body
);
If global styles target .container .modal, they will not apply because the modal is not inside .container in the DOM.
Example
Debugging a component that renders too narrow:
function Sidebar() {
return (
<aside className="sidebar">
<nav className="sidebar-nav">
{/* Content renders too narrow */}
</nav>
</aside>
);
}
Step 1: Check computed styles in DevTools
The nav element has width: fit-content from somewhere.
Step 2: Find source of style
DevTools shows the style comes from a global rule in reset.css:
nav {
width: fit-content;
}
Step 3: Override in component
.sidebar-nav {
width: 100%;
}
TIP: Use DevTools Elements panel to see which CSS rules apply to an element and which are crossed out due to specificity.
Common Mistakes
Measuring before paint
useEffect(() => {
// Runs after DOM update but before paint
const height = ref.current.offsetHeight;
// For visual changes, use useLayoutEffect
}, []);
For accurate measurements:
useLayoutEffect(() => {
// Runs synchronously after DOM update
const height = ref.current.offsetHeight;
}, []);
CSS Modules naming collisions
CSS Modules scope class names but not element selectors:
/* Component.module.css */
.container {
/* Scoped to component */
}
div {
/* Global! Affects all divs */
}
WARNING: Element selectors in CSS Modules are NOT scoped. Only use class selectors in CSS Modules.
Inline style type errors
<div style={{
margin: 10px, // Error: should be string or number
marginTop: '10px', // Correct
marginBottom: 10, // Also correct (treated as px)
}} />
Not handling responsive states
Components may render with server-determined widths that differ from client:
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
setMatches(media.matches);
const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
media.addEventListener('change', listener);
return () => media.removeEventListener('change', listener);
}, [query]);
return matches;
}
Initial render returns false, which may cause layout shift.
Fix with SSR-safe default:
const [matches, setMatches] = useState(() => {
if (typeof window !== 'undefined') {
return window.matchMedia(query).matches;
}
return false; // SSR fallback
});
Conclusion
Debug React layout issues by isolating components, using React DevTools to verify prop flow, and checking CSS specificity in browser DevTools. Watch for portal rendering contexts, CSS Module scoping limits, and hydration mismatches. Measure elements in useLayoutEffect rather than useEffect for accurate dimensions.