Demystifying Debugging With React Developer Tools
انتشار: آذر 15، 1401
بروزرسانی: 23 تیر 1404

Demystifying Debugging With React Developer Tools

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.”

A screenshot displays the same webpage as before on the left, but also shows developer tools on the right of the screen. The developer console displays the contents of the Components tab.

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.

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:

  1. Visit the Chrome plugin page.
  2. Click on Add to Chrome.
  3. Click on Add extension in the pop-up that appears.
  4. Wait until the download is completed.
  5. Click on the extensions (puzzle) icon in the top right corner of your browser.
  6. Click on the pin icon to access the extension easily.
const App = () => {  return <LoginForm defaultUsername="[email protected]" onSubmit={() => {}} />;};
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.

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.

Reload the page after the changes have been made and you’ll see the props inside the Components tab:

With these features, you should be able to optimize an app, find bugs, or pinpoint other issues without much effort.

Installing the Extension

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

const [password, setPassword] = useState("");
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).

The same screenshot as above, with different username/password entries (

The examples above provide a glimpse of simple scenarios. However, React’s API includes more complicated features, such as Context and useReducer.

Our React Developer Tools extension icon now indicates that we are working in the development environment.

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);

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.

To populate our app with these values, we’ll need to update our App.js file:

Screenshot of a complete configuration, showing the login component on the left side, and the profiler activated and outputting results on the right. The results state why the component rendered (Hook 2 changed) and list when it was rendered and at what speed (in milliseconds).

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 Engineering Blog:



منبع
A screenshot of the login component, with the Profiler tab and a pop-up opened on the right. The profiler is set to record why each component rendered while profiling, and the “Hide commits” functionality is not activated.

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}    >// ...

Screenshot of why-did-you-render indicating that Child: f was rerendered because the props object changed.

Redux DevTools

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).

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.

npx create-react-app app-to-debug

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

From left to right, the icon states shown are used when a page:

Screenshot of Redux DevTools inspecting a Medium.com page.

Simplified Problem-solving for Your Next App

You can now reload the page, open the Components tab and see the context in the component tree:

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.

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.

cd app-to-debugnpm start

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 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:

The same screenshot as above, with a pop-up appearing over the Components tab. It displays four tabs (General, Debugging, Components, and Profiler), and shows three options inside the General tab: Theme, Display density, and Highlight updates when components render (which is the selected option). The login component shows a filled password field, and appears highlighted in a yellow frame.

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 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:

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:

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.

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.

A screenshot displaying the login component on the left, with the Components dev tab on the right. The component tree now shows four nested components, from top to bottom: App, AuthenticationContextProvider, Context.Provider, and LoginForm. AuthenticationContextProvider is selected and shows two hooks, Reducer and Callback.

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

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.

When compiled, your app appears in your browser:

We can monitor not only state changes, but also component props. We’ll first modify LoginForm:

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:

import { AuthenticationContextProvider } from "./AuthenticationContext";import LoginForm from "./LoginForm";const App = () => {  return (    <AuthenticationContextProvider>      <LoginForm defaultUsername="[email protected]" />    </AuthenticationContextProvider>  );};export default App;

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:

LoginForm component will be our debugging target, so let’s render it inside App.js:

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 { 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>        <inputid="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;

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:

{ value: { error: undefined, loading: false, token: undefined, logIn: ƒ () {} }}

In the src folder, remove App.css, App.test.js, and logo.svg, then add a new LoginForm.js file as follows:

With our extension up and running, next we’ll create an application to debug.

Creating a Test Application

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.

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 (// ...

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.

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:

A screenshot of the Component tab, displaying the app component and its LoginForm on the left, and a tab for LoginForm on the right with the three hooks states.

Let’s look at the second hook of the LoginForm component:

Four variations of the React logo. From left to right, a blue logo with a black background (production), a white logo with a black background and yellow warning triangle (outdated React), a white logo with no background (no React), and a white logo with a red background and a black bug (development).

import LoginForm from "./LoginForm";const App = () => {  return <LoginForm />;};export default App;

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

A webpage with the URL

Type anything in the username and password fields or click on the check box to see the values in the debugging window update:

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.

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.

There are two nodes added: AuthenticationContextProvider and Context.Provider. The first one is the custom provider we’re using to wrap the application in 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:

Now, whenever you visit a website that is using React, the extension icon you pinned in step 6 will change its appearance: