Intro to debugging ReactJS applications

One of the most important things a developer should learn is how to (properly) debug an application in his language of choice. Knowing that not only allows you to easily find sources of errors in your code, but also teaches you about the inner working of the language thus preventing you from making the same error in the future.

I will cover a couple of techniques and tools you will find useful when developing ReactJS applications. For the sake of this article I will be using Chrome browser, but most of the techniques are universal and the tools exist for different browsers as well.

Please keep in mind that some of the described scenarios assume you have source maps enabled in your build. While debugging an app without sourcemaps enabled is possible it is much harder.

Step 0 — get to know your browser

Most of the tools you need are already available to you, integrated with the browser — I’m talking about the browser’s developer tools. In most cases you can open them by pressing F12 or right clicking and selecting “Inspect”:

Chrome developer tools with Dark theme, inspecting Discordapp.com page

The two most important parts of the Devtools you should be familiar with will be “Console” and “Network”. I will cover those in further sections.

Code linting

While a linter might not help you debug your application it might prevent you from introducing the error in the first place. If you don’t want to spend time configuring and fine-tuning your ESLint (I recommend ESLint here as it’s one of the most popular and powerful) you can use the react-app preset.

Console — your first and most powerful friend

The console window is one of the most important tools at your disposal. Not only does it show all the messages your application is logging using console.log it also shows the majority of error messages in browser caused by your app. On top of that it’s an REPL environment into which you can type any valid JS code and have it executed right away.

console object in JS has a lot of features — if you want to read up on them I highly recommend the page over at MDN.

console is also your “basic” way to debug the application. Want to see if certain part of your code is reached? Is the onClick handler called? What’s the value of that variable? console.log it — in most cases the otput (or lack of it) will allow you to quickly assess what’s wrong with your application.

Keep in mind: console.log when used with an object/array as a parameter can be deceiving. Consider the following code:

const myObject = { name: 'BTM' }
console.log(myObject);
myObject.name = 'John';

What do you expect to see in the output of the log?

Looks ok, right?

“Yes, ‘BTM’, just what I thought!” Is it?

BTM === John (no, not really …)

Ok …. what’s going on here? console.log uses the reference when we expand it with the arrow down, but the value in the collapsed version. Keep that in mind when trying to debug React application — it might look like the valid data is passed to a child component while in fact it is being mutated in parent!

Network tab

There is a very high chance that your application uses some form of communication with a server, and ther ewill be times that you will get wrong data or data will come in a shape you weren’t expecting it. The Network tab is your “go to” here. It will track all HTTP and Websocket requests that happened since you opened the tool (this is sadly not true for Websockets — it will only show communication happening over sockets that were opened after you opened the Developer Tools, so if you need to spy on WS remember to have the tool opened beforehand):

A detailed view of an response from Discord

Important things to keep in mind:

  • a general rule-of-thumb is “red requests failed, white requests are OK”
  • each request can have multiple states, keep an eye out on “Pending” requests — if the status persists for a longer time, it might mean you mistyped the URL or the backend is having troubles
  • when you click on the request a detailed tab will open — “Preview” and “Response” tabs will show your data; if it looks like the data is OK but you can’t parse it in your app, check the “Content-type” in “Headers” tabs — sometimes servers will send you JSON data as text or HTML which can break some tools
  • a very common error which results in data being visible in “Network” tab but not accessible to your app is failing the CORS protocol. Keep an eye out for errors like this in your “Console” section:
CORS request failed

The CORS error is especially tricky as it will show as a successful request (it will be white on the list) and all the data you expect will be there in “Preview” tab. To get around CORS “limitation” you need to either work with the backend provider (in order to allow CORS on the server, or configure it in a way that it can be used by your app) or use a proxy solution (CORS affects only browsers — any proxy written in a server-side language will just ignore it).

React Developer Tools

A deceivingly small browser extension from Facebook will be your go-to tool when checking if the data and JSX structure is OK. You can either download the extension from the library for your browser or grab the standalone version.

After installing the extension you will notice an React icon on the iconbar that highlights whenever you visit an React powered site, and you will see another tab added to the browser Developer Tools. When opened you will see a view similar to HTML structure but instead of HTML nodes you will see the current JSX:

Discord Messages component inspected in React Developer Tools

Clicking on each invidual JSX element will show you its important data: key, ref, its state and props. You can also use the tool to change some of the values (mostly scalar values: booleans, numbers, strings; it does not support changing objects on the fly).

You can use the “Search” box to look up your components by their name but another feature is the automatic lookup of an element you right clicked and selected “Inspect” on (all you have to do is change from “Elements” to “React” tab).

One of the most important features however lies in the update that React Developer Tools adds to the default “Console” window (if you don’t see it when on the “React” tab, click the three-dots icon and select “Show console drawer”). When an React element is selected you can use $r in the console which will give you this of the selected element:

$r = this

You can interact with the element, call its methods and event handlers just like a normal interaction would. This feature allows you to change complex structures we mentioned earlier (e.g. you can select an element, call its $r.setState({message: 'Hello world!'}) which will trigger normal reconcilation process.

Important: if you’re running a production build, names of components will be obfuscated by default. You can get around that by setting a static variable displayName on the component. You can use babel-plugin-add-react-displayname to simplify the process.

Serious debugging — breakpoints

Now that we covered all the “easy” things let’s move into the “real world debugging” — working with breakpoints. A breakpoint is an instruction in code which will stop all the execution and fire up a debugger. There are 2 ways of setting up breakpoints that you will find handy:

  • adding debugger; to your source code — when the parser encounters this directive, and the Developer Tools are open it will trigger a breakpoint
  • finding the point on which we want to break in the Develooper Tools (“Sources” tab) and clicking on the line number — this will add a visual marker and will act exactly the same as above; breakpoints set up this way persist through browser refresh

When code execution reaches this point you will see that the page is in “paused” state and the Devtool now have some additional options:

Breakpoint active

The “Sources” panel shows on which breakpoint the code halted (you can have multiple breakpoints in your application at any given time) and the right hand side shows some interesting information about the current application state, most important are:

  • the navigation panel on the top — allowing us to resume the application, step backwards / forwards in execution or disable all breakpoints
  • the callstack — shows us what functions were called in order to get to the current application state
  • scope — shows what variables are available at the current execution point in what scope (local, global, closure) along with their values; you can also change the values at this point, which — when resumed — will be reflected in your application
  • the console is now hooked to the application state, you have access to all variables shown in “Scope” — this allows you to change values in code that is in other cases not accessible from window namespace (e.g. you get access to components this).

Specific tools

While the above described tools will come in handy in any JS / ReactJS application, there are some tools that you might find useful when working with specific issues / libraries, some of which are:

why did you update?

A small library that integrates with your components that shows you why a component did re-render; while the main goal is to provide ways to check for potential optimizations, it is generally useful when debugging rendering issues

redux-logger

Another small console addition, this time for when you are working with Redux. Main purpose is logging out state changes and actions handled by Redux.

redux-devtools

A more advanced extension useful when working with Redux. Comes in two flavors — a library to add to your application and a browser extension. Main selling point of Redux Devtools is the ability to export / import state as a JSON file (so your testers can share the state with developer when reporting bugs) and the “time travel” which allows you to rewind and fast-forward through a history of Redux reducer states.

redux-immutable-state-invariant

Immutability is one of the main assumptions of Redux, but sometimes a mutation slips through. Adding this middleware will help you track such errors by providing console messages when such a situation takes place.

rubber-duck debugging

No really, hear me out on this one.

Have you ever had a situation where you started to explain a weird bug to a colleague only to have one of those “oh FFS, THIS is what’s wrong” moments? There’s a name for that.

Credit: https://en.wikipedia.org/wiki/Rubber_duck_debugging#/media/File:Rubber_duck_assisting_with_debugging.jpg

Know of any good tools or techniques?

If you know of any good tool or technique that is worth sharing with other React developers, please let me know in the comments bellow :)

I’d like to thank acemarke for his help while writing this post.