Debugging
Preact provides a powerful debugging addon that helps catch common mistakes and provides detailed error messages during development. The preact/debug package enhances your development experience with helpful warnings, prop type checking, and detailed component stack traces.
The debug addon should only be imported in development environments. Never include it in production builds as it adds overhead and exposes detailed error information.
Enabling Debug Mode
To enable debug mode, import preact/debug at the entry point of your application:
import 'preact/debug';
import { render } from 'preact';
import App from './App';
render(<App />, document.getElementById('app'));
Import preact/debug as early as possible in your application to catch errors throughout your component tree.
With your bundler, you can conditionally import debug mode:
if (process.env.NODE_ENV !== 'production') {
require('preact/debug');
}
Error Detection
The debug addon catches many common mistakes and provides detailed error messages:
Undefined Components
When you pass undefined to createElement, debug mode provides a helpful error:
// Forgot to export component
import { ComponentThatDoesntExist } from './components';
// Error: Undefined component passed to createElement()
// You likely forgot to export your component or might have
// mixed up default and named imports
<ComponentThatDoesntExist />
Source: debug/src/debug.js:153-159
Invalid Component Types
Debug mode detects when you accidentally pass JSX literals or invalid types:
// Accidentally passing a JSX literal twice
const MyComponent = <div>Hello</div>;
const vnode = <MyComponent />; // Error!
// Error: Invalid type passed to createElement()
// Did you accidentally pass a JSX literal as JSX twice?
Source: debug/src/debug.js:160-176
Invalid Refs
Refs must be functions or objects created by createRef():
// Invalid: string ref
<div ref="myRef" /> // Error!
// Valid: function ref
<div ref={el => this.myRef = el} />
// Valid: ref object
const myRef = createRef();
<div ref={myRef} />
Source: debug/src/debug.js:178-190
Event Handler Validation
Event handlers must be functions:
// Invalid: non-function event handler
<button onClick="handleClick"> // Error!
// Valid
<button onClick={handleClick}>
Source: debug/src/debug.js:192-208
Render Loop Detection
Debug mode prevents infinite render loops by tracking consecutive renders:
function BadComponent() {
const [state, setState] = useState(0);
// This creates an infinite loop!
setState(state + 1);
return <div>{state}</div>;
}
// Error: Too many re-renders. This is limited to prevent an
// infinite loop which may lock up your browser.
The limit is set to 25 consecutive renders. Source: debug/src/debug.js:262-269
HTML Nesting Validation
Debug mode validates proper HTML nesting according to web standards:
Table Element Nesting
// Error: Improper nesting
<table>
<tr> // Missing <tbody>
<td>Cell</td>
</tr>
</table>
// Valid
<table>
<tbody>
<tr>
<td>Cell</td>
</tr>
</tbody>
</table>
Source: debug/src/debug.js:358-440
Paragraph Element Validation
// Error: Invalid children in <p>
<p>
<div>Block element not allowed</div>
</p>
// Valid
<p>
<span>Inline element is fine</span>
</p>
Source: debug/src/debug.js:416-428
Interactive Content Nesting
// Error: Nested interactive elements
<a href="/link">
<a href="/nested">Nested link</a> // Not allowed!
</a>
<button>
<button>Nested button</button> // Not allowed!
</button>
Source: debug/src/debug.js:429-439
PropTypes Checking
Debug mode automatically checks PropTypes when defined on components:
import PropTypes from 'prop-types';
function MyComponent({ name, age }) {
return <div>{name} is {age} years old</div>;
}
MyComponent.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.isRequired
};
// Error: Failed prop type: Invalid prop `age` of type `string`
// supplied to `MyComponent`, expected `number`
<MyComponent name="John" age="25" />
Source: debug/src/debug.js:210-242, debug/src/check-props.js:24-54
Component Stack Traces
Debug mode provides detailed component stack traces showing where errors occurred:
function Parent() {
return <Child />;
}
function Child() {
return <Broken />; // Undefined component
}
// Error output includes:
// in Child (at Child.js:5)
// in Parent (at Parent.js:2)
Enabling Source Locations
For detailed file and line information, add the Babel plugin:
npm install --save-dev @babel/plugin-transform-react-jsx-source
// .babelrc
{
"plugins": [
["@babel/plugin-transform-react-jsx-source"]
]
}
Without the jsx-source plugin, stack traces will show component names but not file locations.
Source: debug/src/component-stack.js:78-100
Hook Validation
Debug mode validates hook usage:
Hook Call Location
// Error: Hook can only be invoked from render methods
const [state, setState] = useState(0); // Outside component!
function MyComponent() {
return <div>Valid</div>;
}
Source: debug/src/debug.js:274-280
NaN in Dependencies
function MyComponent() {
const value = NaN;
useEffect(() => {
// ...
}, [value]); // Warning: Invalid argument passed to hook
return <div>Component</div>;
}
Source: debug/src/debug.js:470-490
Lifecycle Warnings
Debug mode warns about incorrect lifecycle usage:
setState in Constructor
class MyComponent extends Component {
constructor(props) {
super(props);
this.setState({ foo: 'bar' }); // Warning!
// Use: this.state = { foo: 'bar' }; instead
}
}
Source: debug/src/debug.js:494-511
forceUpdate on Unmounted Components
class MyComponent extends Component {
componentWillUnmount() {
setTimeout(() => {
this.forceUpdate(); // Warning: memory leak!
}, 1000);
}
}
// Warning: Can't call "this.forceUpdate" on an unmounted component.
// This is a no-op, but it indicates a memory leak in your application.
Source: debug/src/debug.js:528-546
Suspense Validation
Debug mode ensures Suspense boundaries are properly configured:
function MyComponent() {
throw Promise.resolve(); // No Suspense boundary!
return <div>Content</div>;
}
// Error: Missing Suspense. The throwing component was: MyComponent
// Fix: Wrap in Suspense
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
Source: debug/src/debug.js:78-99
Duplicate Keys Warning
Debug mode detects duplicate keys in lists:
function List() {
const items = ['a', 'b', 'a']; // Duplicate 'a'
return (
<ul>
{items.map(item => (
<li key={item}>{item}</li> // Warning!
))}
</ul>
);
}
// Warning: Following component has two or more children with the
// same key attribute: "a". This may cause glitches and misbehavior
// in rendering process.
Source: debug/src/debug.js:446-468
Hydration Mismatch Detection
When using SSR with hydration, debug mode detects mismatches:
// Server renders:
<div>Server content</div>
// Client hydrates:
<span>Client content</span>
// Error: Expected a DOM node of type "span" but found "div"
// as available DOM-node(s), this is caused by the SSR'd HTML
// containing different DOM-nodes compared to the hydrated one.
Source: debug/src/debug.js:582-590
Exported Utilities
The debug package exports several utilities for advanced use cases:
resetPropWarnings()
Reset the history of PropTypes warnings (useful in tests):
import { resetPropWarnings } from 'preact/debug';
beforeEach(() => {
resetPropWarnings();
});
Source: debug/src/index.js:6, debug/src/check-props.js:8-10
Component Stack Utilities
import {
captureOwnerStack,
getCurrentVNode,
getDisplayName,
getOwnerStack,
setupComponentStack
} from 'preact/debug';
// Get current rendering component
const vnode = getCurrentVNode();
// Get component name
const name = getDisplayName(vnode);
// Get full component stack
const stack = getOwnerStack(vnode);
Source: debug/src/index.js:8-14
Disabling in Production
Always exclude debug from production builds:
Webpack
// webpack.config.js
module.exports = {
resolve: {
alias: {
'preact/debug': process.env.NODE_ENV === 'production'
? 'preact/debug/noop'
: 'preact/debug'
}
}
};
Vite
// vite.config.js
export default {
resolve: {
alias: {
'preact/debug': process.env.NODE_ENV === 'production'
? 'preact/debug/noop'
: 'preact/debug'
}
}
};
The debug addon adds significant overhead:
- Validates all vnodes before and after rendering
- Checks HTML nesting rules
- Validates PropTypes on every render
- Maintains component stack traces
- Adds ~10-20KB to bundle size
Never ship debug mode to production. It will significantly slow down your application and increase bundle size.