
بروزرسانی: 28 تیر 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.”
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:
- 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.
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 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:
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:
منبع
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} >// ...
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:
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
:
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 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
:
Let’s look at the second hook of the LoginForm
component:
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
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: