Skip to main content

Debugging Web Applications

Debugging is choosing the right tool for the kind of problem:

  • Console for a quick check of a value. It is the simplest tool, not the most powerful one.
  • Breakpoints and debugger to stop the code and inspect its state.
  • Network panel for requests; Performance and Memory panels for slow pages and leaks.
  • React DevTools and Redux DevTools for components and store state.

Details​

Console​

MethodWhat it does
console.log(), warn(), error()prints a message with a level
console.table()shows an array of objects as a table
console.group() / groupEnd()groups related messages
console.count()counts how many times this call has run
console.time() / timeEnd()measures how long an operation takes
console.trace()prints the call stack

The console shows a live reference to an object: when it is expanded, it shows the current state, not the state at the time of logging.

Breakpoints​

TypeWhen it pauses
debuggera statement in the code; works only while DevTools is open
Lineat a line; set by a click on the line number in the Sources panel
Conditionalat a line, only if the expression is true
Logpointdoes not pause: logs an expression without changing the code
DOMwhen a node is removed, or its attributes or subtree change
XHR / fetchwhen a request is sent to a URL that contains a given string
Event listenerwhen an event of a given type is handled
Exceptionswhen an error is thrown, caught or uncaught
  • While paused, the debugger shows the scope variables, the call stack and watch expressions; execution continues with step over, step into and step out.
  • Source maps connect the minified or compiled code to the original source, so breakpoints and stack traces work in TypeScript and JSX. For production they are usually uploaded to an error tracker instead of being public.

DevTools Panels​

PanelWhat it is for
Elementsthe DOM tree, applied and computed styles, the box model
Networkrequests: status, headers, payload, timing; throttling and disabling cache
Performancea recording of the main thread: long tasks, layout, paint, frames
Memoryheap snapshots; comparing two of them shows what leaks
Applicationcookies, localStorage, IndexedDB, service workers
Lighthousean audit of performance, accessibility, SEO and best practices

React DevTools​

A browser extension that adds its own tabs to DevTools:

  • Components: the component tree. For the selected component it shows props, state and hooks, including context values, and lets you edit them in place.
  • Profiler: records renders and shows which components rendered in each commit and how long it took; with the setting "Record why each component rendered while profiling", also why.
  • Suspense (since version 8): the Suspense boundaries and what made a component suspend.

The general setting "Highlight updates when components render" outlines re-rendering components on the page.

Redux DevTools​

  • A log of all dispatched actions with the state and its diff after each one.
  • Time travel: jumping to the state after any action or skipping an action.
  • With Redux Toolkit it works without setup: configureStore enables it.

Example​

const users = [
{ name: 'John', age: 30 },
{ name: 'Jane', age: 25 },
];

console.table(users);

console.time('sort');
users.sort((a, b) => a.age - b.age);
console.timeEnd('sort'); // sort: 0.05ms

function findUser(name) {
debugger; // execution stops here when DevTools is open
return users.find((user) => user.name === name);
}

Follow-up Questions​

  • How do you find a memory leak? Take a heap snapshot, repeat the action, take another one and compare them. See Garbage Collection.
  • How do you find unnecessary re-renders? Record the interaction in the Profiler tab of React DevTools. See Optimization.
  • How do you debug an error that happens only in production? Use the stack trace from an error tracker with source maps, then reproduce it with the same data, network throttling and a production build.

Common Mistakes​

  • Trusting an expanded object in the console: it shows the current state. Log a copy or a primitive value instead.
  • Leaving debugger and console.log in the code; linter rules catch both (no-debugger, no-console).

See Also​