Demystifying Debugging With React Developer Tools
Bugs are often buried deep within the code and software developers spend a great deal of time hunting them down. Many software developers debug front-end code using the browser’s console logging, but this is cumbersome and inefficient. Discover how React Developer Tools can speed up the debugging process by providing powerful tools to inspect components, monitor state and props, and track rendering and performance.
Bugs are often buried deep within the code and software developers spend a great deal of time hunting them down. Many software developers debug front-end code using the browser’s console logging, but this is cumbersome and inefficient. Discover how React Developer Tools can speed up the debugging process by providing powerful tools to inspect components, monitor state and props, and track rendering and performance.
Teimur is a software developer specializing in web application development. He has extensive experience developing with front-end technologies, such as React, TypeScript, and JavaScript, and has implemented applications for innovative companies such as Klarna, where he designed interfaces for interacting with digital wallets from Apple, Google, and Samsung.
Expertise
Previous Role
Front-end EngineerPREVIOUSLY AT
The most experienced software engineers leverage the power of developer tools to save time and increase productivity. The importance of these tools multiplies when the time to debug arrives, as debugging may be the most difficult aspect of software development.
Enter React Developer Tools, a browser extension backed by Meta, the creator of React, and used by 3 million people worldwide. We’ll examine how this tool can elevate your React debugging skills—from inspecting components, states, and props to tracking rendering and performance—all without the hassle of browser console logging.
How to Use React Developer Tools
Web developers must determine the root cause of complex problems in an app on a daily basis. One typical method on the front end is to use multiple console.log
statements and check the browser’s console. This approach can work, but it is not particularly efficient. Fortunately, React Developer Tools makes things easier and allows us to:
- See the React component tree.
- Check and update the state/props of any component in the tree.
- Track the time for a component to render.
- Detect why a component has been re-rendered.
With these features, you should be able to optimize an app, find bugs, or pinpoint other issues without much effort.
Installing the Extension
First, follow these six steps to add the React Developer Tools extension to your browser. We’ll focus on a Chrome setup, but you may follow a similar process for your preferred browser (e.g., Firefox, Edge) if desired:
- Visit the Chrome plugin page.
- Click on Add to Chrome.
- Click on Add extension in the pop-up that appears.
- Wait until the download is completed.
- Click on the extensions (puzzle) icon in the top right corner of your browser.
- Click on the pin icon to access the extension easily.
Now, whenever you visit a website that is using React, the extension icon you pinned in step 6 will change its appearance:
From left to right, the icon states shown are used when a page:
- Uses the production build of React (e.g., https://reactjs.org/).
- Uses an outdated version of React (e.g., https://mozilladevelopers.github.io/playground/).
- Doesn’t use React (e.g., Google).
- Is part of a development environment (we will see this logo when working in our test application).
With our extension up and running, next we’ll create an application to debug.
Creating a Test Application
The create-react-app
utility tool can smoothly bootstrap an app in seconds. As a prerequisite, install Node.js on your machine, then create your app via the command line:
npx create-react-app app-to-debug
The operation might take some time, as it needs to initialize the codebase and install dependencies. As soon as it’s done, go to the application’s root folder and start your new React app:
cd app-to-debug
npm start
When compiled, your app appears in your browser:
Our React Developer Tools extension icon now indicates that we are working in the development environment.
Practical Debugging Methods With React Developer Tools
Let’s move along and learn more about the actual developer tools. First, open the developer console (Option + ⌘ + J on Mac or Shift + CTRL + J on Windows/Linux). There are multiple tabs available (Elements, Console, etc.). The one we need at this stage is Components:
There is just a single component available at this point. That’s correct because our test application has only one component rendered App
(see src/index.js
). Click on the component to show its props, the version of react-dom
used, and the source file.
Tracking Component State
Let’s start with the feature you’ll be using most of the time: checking/editing the state of a component. To demonstrate this functionality, we will make some changes to the test project. We will replace the React placeholder homepage with a simple login form holding three state pieces: a username string, a password string, and a boolean representing a “Remember me” setting.
In the src
folder, remove App.css
, App.test.js
, and logo.svg
, then add a new LoginForm.js
file as follows:
import { useState } from "react";
const LoginForm = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [rememberMe, setRememberMe] = useState(false);
return (
<form
style={{
display: "flex",
flexDirection: "column",
gap: "8px 0",
padding: 16,
}}
>
<input
name="username"
placeholder="Username"
type="text"
value={username}
onChange={(e) => setUsername(e.currentTarget.value)}
/>
<input
name="password"
placeholder="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.currentTarget.value)}
/>
<div>
<input
id="rememberMe"
name="remember_me"
type="checkbox"
checked={rememberMe}
onChange={() => setRememberMe(!rememberMe)}
/>
<label htmlFor="rememberMe">Remember me</label>
</div>
<input type="submit" name="login" value="Log in" />
</form>
);
};
export default LoginForm;
Pay attention to the component declaration approach. We’re using the named component (const LoginForm => …
) to see its name in the dev tools. Anonymous components are displayed as Unknown
.
LoginForm
component will be our debugging target, so let’s render it inside App.js
:
import LoginForm from "./LoginForm";
const App = () => {
return <LoginForm />;
};
export default App;
Return to the browser window with the Components tab open. Now, next to the App
component you’ll see the LoginForm
component. Clicking on LoginForm
will reveal all the state items we’ve created using useState
hooks. Since we haven’t yet entered any text or check box inputs, we see two empty strings and false
:
Type anything in the username and password fields or click on the check box to see the values in the debugging window update:
You may have noticed that there are no names for the state variables. All of them are called State
. This is the expected behavior of the tool because useState
accepts only the value argument (""
or false
in our example). React knows nothing about the name of this state item.
A utility called useDebugValue
partially solves this problem. It can set the display name for custom hooks. For instance, you can set the display name Password
for a custom usePassword
hook.
Monitoring Component Props
We can monitor not only state changes, but also component props. We’ll first modify LoginForm
:
const LoginForm = ({ defaultUsername, onSubmit }) => {
const [username, setUsername] = useState(defaultUsername);
const [password, setPassword] = useState("");
const [rememberMe, setRememberMe] = useState(false);
return (
<form
style={{
display: "flex",
flexDirection: "column",
gap: "8px 0",
padding: 16,
}}
onSubmit={onSubmit}
>
// ...
The code above will add a defaultUsername
property to have a username filled on the initial load, and onSubmit
property to control submit form actions. We also must set these properties’ defaults inside App
:
const App = () => {
return <LoginForm defaultUsername="foo@bar.com" onSubmit={() => {}} />;
};
Reload the page after the changes have been made and you’ll see the props inside the Components tab:
If you need to check how the component will react to a different state/props, you can do it without changing any code. Click on the state/prop value in the Components tab and set the desired value.
Measuring Application Performance
At this point, we should note that tracking props and state is possible via console.log
. However, React Developer Tools offers two advantages over this approach:
- First, logging to the console is unrealistic when scaling a project. The more logs you have the more difficult it is to find what you need.
- Second, monitoring components’ states and properties is only part of the job. If you run into a case when your application works correctly but is slow, React Developer Tools can identify certain performance issues.
For a general overview of an application’s performance, React Developer Tools can highlight DOM updates. Click on the gear icon in the top right corner of the Components tab.
You’ll see a pop-up with four tabs. Click on the General tab and select the Highlight updates when components render check box. Start typing in the password field, and you’ll have your form wrapped with a green/yellow frame. The more updates performed per second, the more highlighted the frame becomes.
For a more detailed performance breakdown, we’ll move from the Components tab to the Profiler tab (don’t forget to uncheck the highlight option).
In the Profiler tab, you will see a blue circle in the top left corner. It’s a button to start profiling your application. As soon as you click on it, all state/props updates will be tracked. Before performing this step, however, we will click on the gear icon in the top right corner of the tab and check the Record why each component rendered while profiling check box. It will extend the functionality of the profiler with the updates’ explanations.
The configuration is now complete, so let’s proceed and profile our app. Close the settings overlay and click on the blue circle button. Start typing in the password field and select the “Remember me” box. Then click the circle button again to stop profiling and see the results.
In the profiling results, you should see itemized updates of the LoginForm
component. Our example shows nine updates: eight for each character in the password field and one for the “Remember me” check box. If you click on any update, you will have an explanation of why the render happened. For example, the first render says “Hook 2 changed.”
Let’s look at the second hook of the LoginForm
component:
const [password, setPassword] = useState("");
Our result makes sense since the second hook is responsible for password state management. If you click on the last render, it will show “Hook 3 changed” because our third hook handles the “Remember me” state.
Viewing React useReducer
and Context
The examples above provide a glimpse of simple scenarios. However, React’s API includes more complicated features, such as Context
and useReducer
.
Let’s add them to our application. First, we will have to add a file with the context. The context we’re going to make will be used for logging a user in and providing the information of the login action. We’ll create the AuthenticationContext.js
file with the following content:
import { useCallback, useContext, useReducer } from "react";
import { createContext } from "react";
const initialState = {
loading: false,
token: undefined,
error: undefined,
};
const AuthenticationContext = createContext({
...initialState,
logIn: () => {},
});
const reducer = (state, action) => {
switch (action.type) {
case "LOG_IN":
return { ...state, loading: true };
case "LOG_IN_SUCCESS":
return { ...state, loading: false, token: action.token };
case "LOG_IN_ERROR":
return { ...state, loading: false, error: action.error };
default:
return action;
}
};
const mockAPICall = async (payload) => ({ token: "TOKEN" });
export const AuthenticationContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const logIn = useCallback(async (payload) => {
try {
dispatch({ type: "LOG_IN" });
const response = await mockAPICall(payload);
dispatch({ type: "LOG_IN_SUCCESS", token: response.token });
} catch (error) {
dispatch({ type: "LOG_IN_ERROR", error });
}
}, []);
return (
<AuthenticationContext.Provider value={{ ...state, logIn }}>
{children}
</AuthenticationContext.Provider>
);
};
export const useAuthentication = () => useContext(AuthenticationContext);
This context will provide the loading status, error, result (token
), and action to perform (logIn
) of our authentication logic. As you can see from the reducer function, initiating the login action will set the loading value to true
. The token will be updated if the response is successful; otherwise, an error will be set. We won’t add a success status value because this value is available through token
(if there is a token, we’ve had a successful operation).
To populate our app with these values, we’ll need to update our App.js
file:
import { AuthenticationContextProvider } from "./AuthenticationContext";
import LoginForm from "./LoginForm";
const App = () => {
return (
<AuthenticationContextProvider>
<LoginForm defaultUsername="foo@bar.com" />
</AuthenticationContextProvider>
);
};
export default App;
You can now reload the page, open the Components
tab and see the context in the component tree:
There are two nodes added: AuthenticationContextProvider
and Context.Provider
. The first one is the custom provider we’re using to wrap the application in the App.js
file. It contains a reducer hook with the current state. The second one is the context representation showing the exact value provided throughout the component tree:
{
value: {
error: undefined,
loading: false,
token: undefined,
logIn: ƒ () {}
}
}
To ensure that React Developer Tools can monitor reducer changes and show the actual state of the context, we’ll adjust the LoginForm.js
file to use the logIn
action as the onSubmit
callback:
import { useCallback, useState } from "react";
import { useAuthentication } from "./AuthenticationContext";
const LoginForm = ({ defaultUsername }) => {
const { logIn } = useAuthentication();
const [username, setUsername] = useState(defaultUsername);
const [password, setPassword] = useState("");
const [rememberMe, setRememberMe] = useState(false);
const onSubmit = useCallback(
async (e) => {
e.preventDefault();
await logIn({ username, password, rememberMe });
},
[username, password, rememberMe, logIn]
);
return (
// ...
Now, if you go back to the browser and click Log in, you’ll see that token
, which used to be undefined
, has an updated value in Context.Provider
’s props.
Supplemental React Debugging Tools
Debugging a React application does not stop at React Developer Tools. Engineers can leverage multiple utilities and their mix of features to create the perfect process for their needs.
Why Did You Render
The first tool worth mentioning is a first-class performance analyst, why-did-you-render. It’s not as simple to use as React Developer Tools, but it enhances render monitoring by explaining each render with human-readable text, with state/props differences and ideas on how to fix issues.
Redux DevTools
Redux is a well-known state management library used by many React engineers, and you can learn more about it by reading my previous article. In short, it consists of two parts: actions and states. Redux DevTools is a user interface representing actions triggered in your app and the consequent state. You can see the add-on in action on a Medium webpage:
Simplified Problem-solving for Your Next App
React Developer Tools is a simple yet powerful addition to your workflow that makes problems easier to fix. Depending on your requirements, you may benefit from additional tools, but React Developer Tools should be your starting point.
With your new React Developer Tools skillset, you will master debugging your next app and can explore any React-based page or app several times faster compared to a traditional code-check.
The editorial team of the Toptal Engineering Blog extends its gratitude to Imam Harir for reviewing the code samples and other technical content presented in this article.
Further Reading on the Toptal Blog:
Understanding the basics
What is React Developer Tools?
React Developer Tools is a browser extension for the React JavaScript library. This set of tools can reveal the insides of any React application and monitor the changes without browser logging and debugging.
What is the use of React Developer Tools?
It can show the React component tree, display and update the state/props, track the rendering time, and detect re-render reasons.
Is it difficult to set up React Developer Tools?
No, setting up React Developer tools simply requires installing an extension to your browser.
Can I debug Context and useReducer with this tool?
Yes, you can use React Developer tools to debug Context and useReducer. It supports each part of the React API out of the box.
Bishkek, Chuy Province, Kyrgyzstan
Member since May 1, 2018
About the author
Teimur is a software developer specializing in web application development. He has extensive experience developing with front-end technologies, such as React, TypeScript, and JavaScript, and has implemented applications for innovative companies such as Klarna, where he designed interfaces for interacting with digital wallets from Apple, Google, and Samsung.
Expertise
Previous Role
Front-end EngineerPREVIOUSLY AT