One place for hosting & domains

      How To Debug React Components Using React Developer Tools


      The author selected Creative Commons to receive a donation as part of the Write for DOnations program.

      Introduction

      Since React apps are made to scale and grow quickly, it’s easy for subtle bugs to infiltrate your code. The React Developer Tools browser extension can help you track down these bugs by giving you more insight into the current state for each component. React Developer Tools gives you an interface for exploring the React component tree along with the current props, state, and context for individual components. React Developer Tools also lets you determine which components are re-rendering and can generate graphs to show how long individual components take to render. You can use this information to track down inefficient code or to optimize data-heavy components.

      This tutorial begins by installing the React Developer Tools browser extension. You’ll then build a text analyzer as a test application, which will take a block of text and display information such as word count, character count, and character usage. Finally, you will use React Developer Tools to explore the text analyzer’s components and keep track of the changing props and context. The examples will use the Chrome browser, but you can also use the plugin for Firefox.

      By the end of this tutorial, you’ll be able to start using the React Developer Tools to debug and explore any React project.

      Prerequisites

      In this step, you’ll install the React Developer Tools broswer extension in Chrome. You’ll use the developer tools in the Chrome JavaScript console to explore the component tree of the debug-tutorial project you made in the Prerequisites. This step will use Chrome, but the steps will be nearly identical for installing the React Developer Tools as an add-on in Firefox.

      By the end of this step, you’ll have the React Developer Tools installed in your browser and you’ll be able to explore and filter components by name.

      The React Developer Tools is a plugin for the Chrome and Firefox browser. When you add the extension, you are adding additional tools to the developer console. Visit the Chrome plugin page for React Developer Tools to install the extension.

      Click on the Add to Chrome button. Then click on the Add extension button to confirm:

      Chrome add extension button

      Chrome will install the extension, and a success message and a new icon will appear in the upper right corner of your browser next to the address bar:

      Chrome success message

      If the icon does not appear, you can add it by clicking on the puzzle piece, then clicking on the pushpin icon by the React Developer Tools:

      Pin extension

      When you are on a page that does not have any React components, the icon will appear gray. However, if you are on a page with React components, the icon will appear blue and green. If you click on the icon, it will indicate that the application is running a production version of React.

      Visit digitalocean.com, to find that the homepage is running a production version of React:

      DigitalOcean React Production Build information

      Now that you are on a website that uses React, open the console to access the React Developer Tools. Open the console by either right-clicking and inspecting an element or by opening the toolbar by clicking View > Developer > JavaScript console.

      When you open the console, you’ll find two new tabs: Components and Profiler:

      Console Open

      The Components tab will show the current React component tree, along with any props, state, or context. The Profiler tab lets you record interactions and measure component rendering. You’ll explore the Profiler tab in Step 3.

      Click on the Components tab to see the current component tree.

      Since this is a production build, the code will be minified and the components will not have descriptive names:

      Components for digitalocean.com in the console

      Now that you’ve tried out React Developer Tools on a working website, you can use it on your test application. If you haven’t started your debug-tutorial application yet, go to a terminal window and run npm start from the root of the project.

      Open a browser to http://localhost:3000.

      Notice that the icon for React Developer Tools is now red and white. If you click on the React Developer Tools icon, you’ll see a warning that the page is in development mode. Since you are still working on the sample application, this is expected.

      Open the console and you’ll find the name of the App component in the Components tab.

      Base Component

      There’s not a lot of information yet, but as you build out the project in the next step, you’ll see all of your components forming a navigable tree.

      In this step, you added the React Developer Tools extension to Chrome. You activated the tools on both a production and a development page, and you briefly explored your debug-tutorial project in the Components tab. In the next step, you’ll build the text analyzer that you’ll use to try out the features of the React Developer Tools.

      Step 2 — Identifying Real-Time Component Props and Context

      In this step, you’ll build a small application to analyze a block of text. The app will determine and report the word count, character count, and character frequency of the text in the input field. As you build the application, you’ll use React Developer Tools to explore the current state and props of each component. You’ll also use React Developer Tools to view the current context in deeply nested components. Finally, you’ll use the tools to identify components that re-render as state changes.

      By the end of this step, you’ll be able to use the React Developer Tools to explore a live application and to observe the current props and state without console statements or debuggers.

      To start, you will create an input component that will take a large amount of text.

      Open the App.js file:

      • nano src/components/App/App.js

      Inside the component, add a div with a class of wrapper, then create a <label> element surrounding a <textarea> element:

      debug-tutorial/src/components/App/App.js

      import React from 'react';
      import './App.css';
      
      function App() {
        return(
          <div className="wrapper">
           <label htmlFor="text">
             Add Your Text Here:
             <br>
             <textarea
               id="text"
               name="text"
               rows="10"
               cols="100"
             >
             </textarea>
            </label>
          </div>
        )
      }
      
      export default App;
      

      This will be the input area for your user. The htmlFor attribute links the label element to elements with an id of text using JSX. You also give the <textarea> component 10 rows and 100 columns to provide room for a large amount of text.

      Save and close the file. Next, open App.css:

      • nano src/components/App/App.css

      Add some styling to the application by replacing the contents with the following:

      debug-tutorial/src/components/App.App.css

      .wrapper {
          padding: 20px;
      }
      
      .wrapper button {
          background: none;
          border: black solid 1px;
          cursor: pointer;
          margin-right: 10px;
      }
      
      .wrapper div {
          margin: 20px 0;
      }
      

      Here you add some padding to the wrapper class, then simplify child <button> elements by removing the background color and adding some margin. Finally, you add a small margin to child <div> elements. These styles will apply to components you will build to display information about the text.

      Save the file. When you do, the browser will refresh and you’ll see the input:

      Text area

      Open App.js:

      • nano src/components/App/App.js

      Next, create a context to hold the value from the <textarea> element. Capture the data using the useState Hook:

      debug-tutorial/src/components/App/App.js

      import React, { createContext, useState } from 'react';
      import './App.css';
      
      export const TextContext = createContext();
      
      function App() {
        const [text, setText] = useState('');
      
        return(
          <TextContext.Provider value={text}>
            <div className="wrapper">
              <label htmlFor="text">
                Add Your Text Here:
                <br>
                <textarea
                  id="text"
                  name="text"
                  rows="10"
                  cols="100"
                  onChange={e => setText(e.target.value)}
                >
                </textarea>
              </label>
            </div>
          </TextContext.Provider>
        )
      }
      
      export default App;
      

      Be sure to export TextContext, then wrap the whole component with the TextContext.Provider. Capture the data by adding an onChange prop to your <textarea> element.

      Save the file. The browser will reload. Be sure that you have React Developer Tools open and notice that App component now shows theContext.Provider as a child component.

      Component context in React Developer Tools

      The component by default has a generic name—Context—but you can change that by adding a displayName property to the generated context. Inside App.js, add a line where you set the displayName to TextContext:

      debug-tutorial/src/components/App/App.js

      import React, { createContext, useState } from 'react';
      import './App.css';
      
      export const TextContext = createContext();
      TextContext.displayName="TextContext";
      
      function App() {
          ...
      }
      
      export default App;
      

      It is not necessary to add a displayName, but it does help to navigate components when analyzing the component tree in the console. You will also see the value of the useState Hook in the side bar. Type some text in the input and you’ll see the updated value in React Developer Tools under the hooks section on the App component.

      Update Value in Developer Tools

      The Hook also has a generic name of State, but this is not as easy to update as the context. There is a useDebugValue Hook, but it only works on custom Hooks and is not recommended for all custom Hooks.

      In this case, the state for the App component is the prop to TextContext.Provider. Click on the TextContext.Provider in the React Developer Tools and you’ll see that the value also reflects the input value that you set with the state:

      Updated value for the context

      React Developer Tools is showing you real time prop and context information, and the value will grow as you add components.

      Next, add a component called TextInformation. This component will be a container for the components with specific data analysis, such as the word count.

      First, make the directory:

      • mkdir src/components/TextInformation

      Then open TextInformation.js in your text editor.

      • nano src/components/TextInformation/TextInformation.js

      Inside the component, you will have three separate components: CharacterCount, WordCount, and CharacterMap. You’ll make these components in just a moment.

      The TextInformation component will use the useReducer Hook to toggle the display of each component. Create a reducer function that toggles the display value of each component and a button to toggle each component with an onClick action:

      debug-tutorial/src/components/TextInformation/TextInformation.js

      import React, { useReducer } from 'react';
      
      const reducer = (state, action) => {
        return {
          ...state,
          [action]: !state[action]
        }
      }
      export default function TextInformation() {
        const [tabs, toggleTabs] = useReducer(reducer, {
          characterCount: true,
          wordCount: true,
          characterMap: true
        });
      
        return(
          <div>
            <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
            <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
            <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
          </div>
        )
      }
      

      Notice that your useReducer Hook starts with an object that maps each key to a boolean. The reducer function uses the spread operator to preserve the previous value while setting a new value using the action parameter.

      Save and close the file. Then open App.js:

      • nano src/components/App/App.js

      Add in your new component:

      debug-tutorial/src/components/App/App.js

      import React, { createContext, useState } from 'react';
      import './App.css';
      import TextInformation from '../TextInformation/TextInformation';
      
      ...
      
      function App() {
        const [text, setText] = useState('');
      
        return(
          <TextContext.Provider value={text}>
            <div className="wrapper">
              <label htmlFor="text">
                Add Your Text Here:
                <br>
                <textarea
                  id="text"
                  name="text"
                  rows="10"
                  cols="100"
                  onChange={e => setText(e.target.value)}
                >
                </textarea>
              </label>
              <TextInformation />
            </div>
          </TextContext.Provider>
        )
      }
      
      export default App;
      

      Save and close the file. When you do, the browser will reload, and you’ll see the updated component. If you click on TextInformation in React Developer Tools, you’ll see the value update on each button click:

      Update Reducer on Click

      Now that you have the container component, you’ll need to create each informational component. Each component will take a prop called show. If show is falsy, the component will return null. The components will consume the TextContext, analyze the data, and display the result.

      To start, create the CharacterCount component.

      First, make a new directory:

      • mkdir src/components/CharacterCount

      Then, open CharacterCount.js in your text editor:

      • nano src/components/CharacterCount/CharacterCount.js

      Inside the component, create a function that uses the show prop and displays null if show is falsy:

      debug-tutorial/src/components/CharacterCount/CharacterCount.js

      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      export default function CharacterCount({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
            Character Count: {text.length}
          </div>
        )
      }
      
      CharacterCount.proTypes = {
        show: PropTypes.bool.isRequired
      }
      

      Inside the CharacterCount function, you assign the value of TextContext to a variable using the useContext Hook. You then return a <div> that shows the character count using the length method. Finally, PropTypes adds a weak typing system to provide some enforcement to make sure the wrong prop type is not passed.

      Save and close the file. Open TextInformation.js:

      • nano src/components/TextInformation/TextInformation.js

      Import CharacterCount and add the component after the buttons, passing tabs.characterCount as the show prop:

      debug-tutorial/src/components/TextInformation/TextInformation.js

      import React, { useReducer } from 'react';
      import CharacterCount from '../CharacterCount/CharacterCount';
      
      const reducer = (state, action) => {
        return {
          ...state,
          [action]: !state[action]
        }
      }
      
      export default function TextInformation() {
        const [tabs, toggleTabs] = useReducer(reducer, {
          characterCount: true,
          wordCount: true,
          characterMap: true
        });
      
        return(
          <div>
            <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
            <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
            <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
            <CharacterCount show={tabs.characterCount} />
          </div>
        )
      }
      

      Save the file. The browser will reload and you’ll see the component in the React Developer Tools. Notice that as you add words in the input, the context will update. If you toggle the component, you’ll see the props update after each click:

      Adding text and toggling

      You can also manually add or change a prop by clicking on the property and updating the value:

      Manually Changing Props

      Next, add a WordCount component.

      Create the directory:

      • mkdir src/components/WordCount

      Open the file in a text editor:

      • nano src/components/WordCount/WordCount.js

      Make a component that is similar to CharacterCount, but use the split method on spaces to create an array of words before showing the length:

      debug-tutorial/src/components/WordCount/WordCount.js

      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      export default function WordCount({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
            Word Count: {text.split(' ').length}
          </div>
        )
      }
      
      WordCount.proTypes = {
        show: PropTypes.bool.isRequired
      }
      

      Save and close the file.

      Finally, create a CharacterMap component. This component will show how often a specific character is used in a block of text. It will then sort the characters by frequency in the passage and display the results.

      First, make the directory:

      • mkdir src/components/CharacterMap

      Next, open CharacterMap.js in a text editor:

      • nano src/components/CharacterMap/CharacterMap.js

      Import and use the TextContext component and use the show prop to display results as you did in the previous components:

      debug-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      export default function CharacterMap({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
            Character Map: {text.length}
          </div>
        )
      }
      
      CharacterMap.proTypes = {
        show: PropTypes.bool.isRequired
      }
      

      This component will need a slightly more complicated function to create the frequency map for each letter. You’ll need to go through each character and increment a value anytime there is a repeat. Then you’ll need to take that data and sort it so that the most frequent letters are at the top of the list.

      To do this, add the following highlighted code:

      debug-tutorial/src/components/CharacterMap/CharacterMap.js

      
      import React, { useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      function itemize(text){
       const letters = text.split('')
         .filter(l => l !== ' ')
         .reduce((collection, item) => {
           const letter = item.toLowerCase();
           return {
             ...collection,
             [letter]: (collection[letter] || 0) + 1
           }
         }, {})
       return Object.entries(letters)
         .sort((a, b) => b[1] - a[1]);
      }
      
      export default function CharacterMap({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
           Character Map:
           {itemize(text).map(character => (
             <div key={character[0]}>
               {character[0]}: {character[1]}
             </div>
           ))}
          </div>
        )
      }
      
      CharacterMap.proTypes = {
        show: PropTypes.bool.isRequired
      }
      

      In this code, you create a function called itemize that splits the text into an array of characters using the split() string method. Then you reduce the array to an object by adding the character and then incrementing the count for each subsequent character. Finally, you convert the object to an array of pairs using Object.entries and sort to put the most used characters at the top.

      After you create the function, you pass the text to the function in the render method and map over the results to display the character—array value [0]—and the count—array value [1]—inside a <div>.

      Save and close the file. This function will give you an opportunity to explore some performance features of the React Developer Tools in the next section.

      Next, add the new components to TextInformation and look at the values in React Developer Tools.

      Open TextInformation.js:

      • nano src/components/TextInformation/TextInformation.js

      Import and render the new components:

      debug-tutorial/src/components/TextInformation/TextInformation.js

      import React, { useReducer } from 'react';
      import CharacterCount from '../CharacterCount/CharacterCount';
      import CharacterMap from '../CharacterMap/CharacterMap';
      import WordCount from '../WordCount/WordCount';
      
      const reducer = (state, action) => {
        return {
          ...state,
          [action]: !state[action]
        }
      }
      
      export default function TextInformation() {
        const [tabs, toggleTabs] = useReducer(reducer, {
          characterCount: true,
          wordCount: true,
          characterMap: true
        });
      
        return(
          <div>
            <button onClick={() => toggleTabs('characterCount')}>Character Count</button>
            <button onClick={() => toggleTabs('wordCount')}>Word Count</button>
            <button onClick={() => toggleTabs('characterMap')}>Character Map</button>
            <CharacterCount show={tabs.characterCount} />
            <WordCount show={tabs.wordCount} />
            <CharacterMap show={tabs.characterMap} />
          </div>
        )
      }
      

      Save and close the file. When you do, the browser will refresh, and if you add in some data, you’ll find the character frequency analysis in the new components:

      CharacterMap Component in React Developer Tools

      In this section, you used React Developer Tools to explore the component tree. You also learned how to see the real-time props for each component and how to manually change the props using the developer tools. Finally, you viewed the context for the component change with input.

      In the next section, you’ll use the React Developer Tools Profiler tab to identify components that have long render times.

      Step 3 — Tracking Component Rendering Across Interactions

      In this step, you’ll use the React Developer Tools profiler to track component rendering and re-rendering as you use the sample application. You’ll navigate flamegraphs, or visualizations of your app’s relevant optimization metrics, and use the information to identify inefficient components, reduce rendering time, and increase application speed.

      By the end of this step, you’ll know how to identify components that render during user interactions and how to compose components to reduce inefficient rendering.

      A quick way to see how components change each other is to enable highlighting when a component is re-rendered. This will give you a visual overview of how the components respond to changing data.

      In React Developer Tools, click on the Settings icon. It will look like a gear:

      Settings icon

      Then select the option under General that says Highlight updates when components render.

      Highlight changes

      When you make any changes, React Developer Tools will highlight components that re-render. For example, when you change input, every component re-renders because the data is stored on a Hook at the root level and every change will re-render the whole component tree.

      Notice the highlighting around the components, including the top of the screen around the root component:

      Highlighting Text

      Compare that to how the components re-render when you click on one of the buttons to toggle the data. If you click one of the buttons, the components under TextInformation will re-render, but not the root component:

      Rerendering lower components only

      Showing the re-renders will give you a quick idea of how the components are related, but it doesn’t give you a lot of data to analyze specific components. To gain more insight, let’s look at the profiler tools.

      The profiler tools are designed to help you measure precisely how long each component takes to render. This can help you identify components that may be slow or process intense.

      Re-open the settings and uncheck the box for Highlight updates when components render. Then click on the Profiler tab in the console.

      To use the profiler, click the blue circle on the left of the screen to begin recording and click it again when you are finished:

      Start profiling

      When you stop recording, you’ll find a graph of the component changes including, how long each item took to render.

      To get a good sense of the relative efficiency of the components, paste in the Wikipedia page for Creative Commons. This text is long enough to give interesting results, but not so big that it will crash the application.

      After pasting in the text, start the profiler, then make a small change to the input. Stop profiling after the component has finished re-rendering. There will be a long pause, because the application is handling a long re-rendering:

      Adding a change with a lot of text

      When you end the recording, React Developer Tools will create a flamegraph that shows every component that re-rendered and how long it took to re-render each component.

      In this case, every keypress from the word “Change” causes a re-render. More importantly, it shows how long each render takes and why there was a long delay. The components App, TextContext.Provider, and TextInformation take about .2 milliseconds to rerender. But the CharacterMap component takes around 1 second per keystroke to re-render because of the complicated data parsing in the itemize function.

      In the display, each yellow bar is a new keystroke. You can replay the sequence one at a time by clicking on each bar. Notice that there is slight variation in the render times, but the CharacterMap is consistently slow:

      Looking at the flamegraph

      You can get more information by selecting the option Record why each component rendered while profiling. under the Profiler section of the settings.

      Try toggling the Word Count component and notice how long the changes take. The application still lags even though you haven’t changed the text content:

      Word Count toggle flamegraph

      Now when you hover your cursor over a component, you’ll find that it includes a reason the component re-rendered. In this case, the reason the component changed is The parent component rendered. That’s a problem for the CharacterMap component. CharacterMap is doing an expensive calculation every time the parent changes, even if the props and the context do not change. That is, it’s recalculating data even though the data is identical to the previous render.

      Click on the Ranked tab and you’ll find how much more time CharacterMap takes when compared to all other components:

      Ranked Tab

      React Developer Tools have helped isolate a problem: the CharacterMap component re-renders and performs an expensive calculation anytime any parent component changes.

      There are multiple ways to solve the problem, but they all involve some sort of caching via memoization, a process by which already calculated data is remembered rather than recalculated. You can either use a library like lodash/memoize or memoize-one to cache the results of the itemize function, or you can use the built in React memo function to memoize the whole component.

      If you use the React memo, the function will only re-render if the props or context change. In this case, you’ll use React memo. In general, you should memoize the data itself first since it’s a more isolated case, but there are some interesting changes in the React Developer Tools if you memoize the whole component, so you’ll use that approach in this tutorial.

      Open CharacterMap.js:

      • nano src/components/CharacterMap/CharacterMap.js

      Import memo from React, then pass the entire function into the memo function:

      debug-tutorial/src/components/CharacterMap/CharacterMap.js

      import React, { memo, useContext } from 'react';
      import PropTypes from 'prop-types';
      import { TextContext } from '../App/App';
      
      ...
      
      function CharacterMap({ show }) {
        const text = useContext(TextContext);
      
        if(!show) {
          return null;
        }
      
        return(
          <div>
           Character Map:
           {itemize(text).map(character => (
             <div key={character[0]}>
               {character[0]}: {character[1]}
             </div>
           ))}
          </div>
        )
      }
      
      CharacterMap.proTypes = {
        show: PropTypes.bool.isRequired
      }
      
      export default memo(CharacterMap);
      
      

      You move the export default line to the end of the code in order to pass the component to memo right before exporting. After that, React will compare the props before re-rendering.

      Save and close the file. The browser will reload, and when you toggle the WordCount, the component will update much faster. This time, CharacterMap does not re-render. Instead, in React Developer Tools, you’ll see a gray rectangle showing re-rendering was prevented.

      React Developer Tools showing that CharacterMap did not re-render

      If you look at the Ranked tab, you’ll find that both the CharacterCount and the WordCount re-rendered, but for different reasons. Since CharacterCount is not memoized, it re-rendered because the parent changed. The WordCount re-rendered because the props changed. Even if it was wrapped in memo, it would still rerender.

      Ranked view of memoized app

      Note: Memoizing is helpful, but you should only use it when you have a clear performance problem, as you did in this case. Otherwise, it can create a performance problem: React will have to check props every time it re-renders, which can cause a delay on smaller components.

      In this step, you used the profiler to identify re-renders and componenent re-rendering. You also used flamegraphs and ranked graphs to identify components that are slow to re-render and then used the memo function to prevent re-rendering when there are no changes to the props or context.

      Conclusion

      The React Developer Tools browser extension gives you a powerful set of utilities to explore your components in their applications. With these tools, you’ll be able to explore a component’s state and identify bugs using real data without console statements or debuggers. You can also use the profiler to explore how components interact with each other, allowing you to identify and optimize components that have slow rendering in your full application. These tools are a crucial part of the development process and give you an opportunity to explore the components as part of an application and not just as static code.

      If you would like to learn more about debugging JavaScript, see our article on How To Debug Node.js with the Built-In Debugger and Chrome DevTools. For more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.



      Source link

      3 Ways to Debug Tech’s Diversity Gap in 2020


      Silicon Valley is struggling with a bit of an image problem. That image? Straight, white, male.

      In 2018, women filled only 25% of all computing-related occupations — which is about the same percentage that we saw in the 1960s. For African-American and Hispanic populations, the representation in these fields is far below the national distribution.

      And at the intersection of race and gender, the state of women in tech is even bleaker: 65% of women in computing occupations are white, 19% are Asian/Pacific Islander, only 7% are African American, and 7% are Latina.

      In 2019, computer programmers are no longer high-school geeks, but meritocratic winners who wield considerable power in society. Engineers at Facebook — or more precisely, the algorithms they program — decide what news we see and what ads we get served.

      (If you think that ads aren’t linked to economic opportunity, think again.)

      Many formerly analog tasks — hailing a taxi, dimming the lights — now rely on code that only programmers can hope to understand fully. If women and minorities are left out of coding jobs now, that omission could have ramifications on the structure of our society for years to come.

      It’s clear by now that social and environmental forces contribute to the differences in earning potential for women and minorities, and that these forces also hold the same people back from careers in STEM (Science, Technology, Engineering, and Math). What needs to be done? Let’s take a look at how to debug the diversity gap.

      DreamHost Takes Inclusivity Seriously

      We regularly report on diversity, accessibility, and representation in the tech workforce. Subscribe to our monthly newsletter so you never miss an article.

      1. Give Every Student Access to Computer Science Classes

      Early exposure to skills is crucial for securing a job in one of the best-paid and fastest-growing industries around. Yet only 35% of high schools nationwide are currently teaching computer science classes. Some schools in the U.S. are exposing young people to the basics of programming, which serves to improve their familiarity and comfort with these subjects. But to open the doors of the tech meritocracy to the underprivileged, coding needs to be taught in public schools, as early as possible — even in elementary school.

      There are a lot of barriers to this.

      Because the public education system in the U.S. depends heavily on local control, it’s impossible to design and implement sweeping changes to curricula in one fell swoop. National standards like Common Core and testing-focused federal programs like No Child Left Behind often leave little room for enrichment classes or electives.

      In some cases, nonprofits and businesses are stepping in to fill the gap; for instance, this year Google pledged $25 million to support programs that help Black and Latino students have access to computer science education. But a charity initiative here or there isn’t likely to create broad-based change.

      It also won’t be enough for schools just to offer coding classes: the coding gap will only close with specific outreach to marginalized groups. There is substantial data to suggest that a learned lack of confidence can discourage minority groups from choosing certain subjects in school. And one 2016 study found that boys and girls begin school achieving in math at similar rates, with a gender gap appearing as early as third grade — this is significant, as previous research suggests that early achievement in math predicts interest and confidence in the subject in middle and high school.

      Even more concerning, the study indicated that elementary school teachers perceive girls with nearly identical math scores (and classroom behavior) to be less proficient in math than boys.

      This unconscious bias contributes to female students lacking confidence and performing worse in future math classes.  Unless teachers work to recruit girls and minorities to coding classes and overcome their own perceptions that girls aren’t as good at math, such biases will continue to keep their numbers in the tech sector low.

      2. Expand the Scope of Nonprofits

      We’ve certainly been entering the Era of the Nonprofit for the past few years, and nonprofits that aim to teach coding to women and people of color abound. (A few examples: #YesWeCode. Girls Who Code. Black Girls Code.)

      Lack of access to training isn’t the only issues these groups face. In the case of underprivileged youth, for instance, a major challenge is the limited access some of these underrepresented students have to computers.

      But the challenges extend beyond the physical, especially when it comes to connecting students with jobs that utilize their training. Limitations experienced in this realm — such as the absence of a professional network or an unfriendly corporate culture — can prevent any would-be software engineer or developer from thriving. Successful nonprofit coding programs will be those that succeed in the final stretch: job placement, hiring, and support during the transition.

      3. Retain Diverse Talent in the Workplace

      It’s not just a lack of candidates in the pipeline that’s keeping representation low; it’s also a lack of retention. Support needs to continue after coders become established in their careers. At 10 to 20 years into their tech careers,  56% of women leave the field, at a quit-rate double that of men.

      Why are they leaving? One small study found that the most common reasons women leave tech jobs are a lack of opportunity for career growth, poor management, and the gender pay gap. Older research cites poor workplaces including few opportunities for development and training, little support for outside-of-work responsibilities, and undermining bosses.

      A 2019 study published in Nature found that nearly half of women in science leave after having their first child, compared to 23% of men. Clearly, something needs to be done to better support parents in STEM fields, particularly working mothers.

      There are a number of ways to support and retain female and minority coders, starting with simply calling out their accomplishments and good ideas. Nonprofits that encourage professional networking, like Women Who Code, can certainly help women find their tribe in the industry, but in the end, it will be up to tech companies themselves to enact policies to retain female talent.

      The Reality of the Diversity Gap in the Tech World

      The tech industry is booming, which, in theory, should mean more demand for programming labor. But with barriers to intercontinental communication quickly vanishing, more and more programming and web-design jobs based in the U.S. are being outsourced to lower-paid workers in other countries. In fact, computer programming jobs are projected to decrease by 7% over the next eight years in the U.S., even as the computer technology industry is expected to grow by 12%.

      Whether computer programming serves to be an equalizer or perpetuator of inequality in the U.S. may depend on how fast minority groups can participate and get a “piece of the pie,” so to speak, before the available opportunities shrink.

      The bad news is that it’s looking like underrepresented groups will still have to try twice as hard for a shot at the same jobs, which is truly unfair.

      The good news is that people are more aware than ever before that the diversity gap in tech is a real problem. Ultimately, the U.S. education system will adapt, nonprofits will grow, and more female and minority students will find — and stay in — careers in computing-related tasks.

      After all, diverse teams are the only way companies will keep up with the changing demands of a world where computers are not going away.



      Source link

      How To Debug Node.js with the Built-In Debugger and Chrome DevTools


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      In Node.js development, tracing a coding error back to its source can save a lot of time over the course of a project. But as a program grows in complexity, it becomes harder and harder to do this efficiently. To solve this problem, developers use tools like a debugger, a program that allows developers to inspect their program as it runs. By replaying the code line-by-line and observing how it changes the program’s state, debuggers can provide insight into how a program is running, making it easier to find bugs.

      A common practice programmers use to track bugs in their code is to print statements as the program runs. In Node.js, that involves adding extra console.log() or console.debug() statements in their modules. While this technique can be used quickly, it is also manual, making it less scalable and more prone to errors. Using this method, it is possible to mistakenly log sensitive information to the console, which could provide malicious agents with private information about customers or your application. On the other hand, debuggers provide a systematic way to observe what’s happening in a program, without exposing your program to security threats.

      The key features of debuggers are watching objects and adding breakpoints. By watching objects, a debugger can help track the changes of a variable as the programmer steps through a program. Breakpoints are markers that a programmer can place in their code to stop the code from continuing beyond points that the developer is investigating.

      In this article, you will use a debugger to debug some sample Node.js applications. You will first debug code using the built-in Node.js debugger tool, setting up watchers and breakpoints so you can find the root cause of a bug. You will then use Google Chrome DevTools as a Graphical User Interface (GUI) alternative to the command line Node.js debugger.

      Prerequisites

      Step 1 — Using Watchers with the Node.js Debugger

      Debuggers are primarily useful for two features: their ability to watch variables and observe how they change when a program is run and their ability to stop and start code execution at different locations called breakpoints. In this step, we will run through how to watch variables to identify errors in code.

      Watching variables as we step through code gives us insight into how the values of variables change as the program runs. Let’s practice watching variables to help us find and fix logical errors in our code with an example.

      We begin by setting up our coding environment. In your terminal, create a new folder called debugging:

      Now enter that folder:

      Open a new file called badLoop.js. We will use nano as it’s available in the terminal:

      Our code will iterate over an array and add numbers into a total sum, which in our example will be used to add up the number of daily orders over the course of a week at a store. The program will return the sum of all the numbers in the array. In the editor, enter the following code:

      debugging/badLoop.js

      let orders = [341, 454, 198, 264, 307];
      
      let totalOrders = 0;
      
      for (let i = 0; i <= orders.length; i++) {
        totalOrders += orders[i];
      }
      
      console.log(totalOrders);
      

      We start by creating the orders array, which stores five numbers. We then initialize totalOrders to 0, as it will store the total of the five numbers. In the for loop, we iteratively add each value in orders to totalOrders. Finally, we print the total amount of orders at the end of the program.

      Save and exit from the editor. Now run this program with node:

      The terminal will show this output:

      Output

      NaN

      NaN in JavaScript means Not a Number. Given that all the input are valid numbers, this is unexpected behavior. To find the error, let’s use the Node.js debugger to see what happens to the two variables that are changed in the for loop: totalOrders and i.

      When we want to use the built-in Node.js debugger on a program, we include inspect before the file name. In your terminal, run the node command with this debugger option as follows:

      When you start the debugger, you will find output like this:

      Output

      < Debugger listening on ws://127.0.0.1:9229/e1ebba25-04b8-410b-811e-8a0c0902717a < For help, see: https://nodejs.org/en/docs/inspector < Debugger attached. Break on start in badLoop.js:1 > 1 let orders = [341, 454, 198, 264, 307]; 2 3 let totalOrders = 0;

      The first line shows us the URL of our debug server. That’s used when we want to debug with external clients, like a web browser as we’ll see later on. Note that this server listens on port :9229 of the localhost (127.0.0.1) by default. For security reasons, it is recommended to avoid exposing this port to the public.

      After the debugger is attached, the debugger outputs Break on start in badLoop.js:1.

      Breakpoints are places in our code where we’d like execution to stop. By default, Node.js’s debugger stops execution at the beginning of the file.

      The debugger then shows us a snippet of code, followed by a special debug prompt:

      Output

      ... > 1 let orders = [341, 454, 198, 264, 307]; 2 3 let totalOrders = 0; debug>

      The > next to 1 indicates which line we’ve reached in our execution, and the prompt is where we will type in our commends to the debugger. When this output appears, the debugger is ready to accept commands.

      When using a debugger, we step through code by telling the debugger to go to the next line that the program will execute. Node.js allows the following commands to use a debugger:

      • c or cont: Continue execution to the next breakpoint or to the end of the program.
      • n or next: Move to the next line of code.
      • s or step: Step into a function. By default, we only step through code in the block or scope we’re debugging. By stepping into a function, we can inspect the code of the function our code calls and observe how it reacts to our data.
      • o: Step out of a function. After stepping into a function, the debugger goes back to the main file when the function returns. We can use this command to go back to the original function we were debugging before the function has finished execution.
      • pause: Pause the running code.

      We’ll be stepping through this code line-by-line. Press n to go to the next line:

      Our debugger will now be stuck on the third line of code:

      Output

      break in badLoop.js:3 1 let orders = [341, 454, 198, 264, 307]; 2 > 3 let totalOrders = 0; 4 5 for (let i = 0; i <= orders.length; i++) {

      Empty lines are skipped for convenience. If we press n once more in the debug console, our debugger will be situated on the fifth line of code:

      Output

      break in badLoop.js:5 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      We are now beginning our loop. If the terminal supports color, the 0 in let i = 0 will be highlighted. The debugger highlights the part of the code the program is about to execute, and in a for loop, the counter initialization is executed first. From here, we can watch to see why totalOrders is returning NaN instead of a number. In this loop, two variables are changed every iteration—totalOrders and i. Let’s set up watchers for both of those variables.

      We’ll first add a watcher for the totalOrders variable. In the interactive shell, enter this:

      To watch a variable, we use the built-in watch() function with a string argument that contains the variable name. As we press ENTER on the watch() function, the prompt will move to the next line without providing feedback, but the watch word will be visible when we move the debugger to the next line.

      Now let’s add a watcher for the variable i:

      Now we can see our watchers in action. Press n to go to the next step. The debug console will show this:

      Output

      break in badLoop.js:5 Watchers: 0: totalOrders = 0 1: i = 0 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      The debugger now displays the values of totalOrders and i before showing the line of code, as shown in the output. These values are updated every time a line of code changes them.

      At this point, the debugger is highlighting length in orders.length. This means the program is about to check the condition before it executes the code within its block. After the code is executed, the final expression i++ will be executed. You can read more about for loops and their execution in our How To Construct For Loops in JavaScript guide.

      Enter n in the console to enter the for loop’s body:

      Output

      break in badLoop.js:6 Watchers: 0: totalOrders = 0 1: i = 0 4 5 for (let i = 0; i <= orders.length; i++) { > 6 totalOrders += orders[i]; 7 } 8

      This step updates the totalOrders variable. Therefore, after this step is complete our variable and watcher will be updated.

      Press n to confirm. You will see this:

      Output

      Watchers: 0: totalOrders = 341 1: i = 0 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      As highlighted, totalOrders now has the value of the first order: 341.

      Our debugger is just about to process the final condition of the loop. Enter n so we execute this line and update i:

      Output

      break in badLoop.js:5 Watchers: 0: totalOrders = 341 1: i = 1 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      After initialization, we had to step through the code four times to see the variables updated. Stepping through the code like this can be tedious; this problem will be addressed with breakpoints in Step 2. But for now, by setting up our watchers, we are ready to observe their values and find our problem.

      Step through the program by entering n twelve more times, observing the output. Your console will display this:

      Output

      break in badLoop.js:5 Watchers: 0: totalOrders = 1564 1: i = 5 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      Recall that our orders array has five items, and i is now at position 5. But since i is used as the index of an array, there is no value at orders[5]; the last value of the orders array is at index 4. This means that orders[5] will have a value of undefined.

      Type n in the console and you’ll observe that the code in the loop is executed:

      Output

      break in badLoop.js:6 Watchers: 0: totalOrders = 1564 1: i = 5 4 5 for (let i = 0; i <= orders.length; i++) { > 6 totalOrders += orders[i]; 7 } 8

      Typing n once more shows the value of totalOrders after that iteration:

      Output

      break in badLoop.js:5 Watchers: 0: totalOrders = NaN 1: i = 5 3 let totalOrders = 0; 4 > 5 for (let i = 0; i <= orders.length; i++) { 6 totalOrders += orders[i]; 7 }

      Through debugging and watching totalOrders and i, we can see that our loop is iterating six times instead of five. When i is 5, orders[5] is added to totalOrders. Since orders[5] is undefined, adding this to a number will yield NaN. The problem with our code therefore lies within our for loop’s condition. Instead of checking if i is less than or equal to the length of the orders array, we should only check that it’s less than the length.

      Let’s exit our debugger, make the changes and run the code again. In the debug prompt, type the exit command and press ENTER:

      Now that you’ve exited the debugger, open badLoop.js in your text editor:

      Change the for loop’s condition:

      debugger/badLoop.js

      ...
      for (let i = 0; i < orders.length; i++) {
      ...
      

      Save and exit nano. Now let’s execute our script like this:

      When it’s complete, the correct result will be printed:

      Output

      1564

      In this section, we used the debugger’s watch command to find a bug in our code, fixed it, and watched it work as expected.

      Now that we have some experience with the basic use of the debugger to watch variables, let’s look at how we can use breakpoints so that we can debug without stepping through all the lines of code from the start of the program.

      Step 2 — Using Breakpoints With the Node.js Debugger

      It’s common for Node.js projects to consist of many interconnected modules. Debugging each module line-by-line would be time consuming, especially as an app scales in complexity. To solve this problem, breakpoints allow us to jump to a line of code where we’d like to pause execution and inspect the program.

      When debugging in Node.js, we add a breakpoint by adding the debugger keyword directly to our code. We can then go from one breakpoint to the next by pressing c in the debugger console instead of n. At each breakpoint, we can set up watchers for expressions of interest.

      Let’s see this with an example. In this step, we’ll set up a program that reads a list of sentences and determines the most common word used throughout all the text. Our sample code will return the first word with the highest number of occurrences.

      For this exercise, we will create three files. The first file, sentences.txt, will contain the raw data that our program will process. We’ll add the beginning text from Encyclopaedia Britannica’s article on the Whale Shark as sample data, with the punctuation removed.

      Open the file in your text editor:

      Next, enter the following code:

      debugger/sentences.txt

      Whale shark Rhincodon typus gigantic but harmless shark family Rhincodontidae that is the largest living fish
      Whale sharks are found in marine environments worldwide but mainly in tropical oceans
      They make up the only species of the genus Rhincodon and are classified within the order Orectolobiformes a group containing the carpet sharks
      The whale shark is enormous and reportedly capable of reaching a maximum length of about 18 metres 59 feet
      Most specimens that have been studied however weighed about 15 tons about 14 metric tons and averaged about 12 metres 39 feet in length
      The body coloration is distinctive
      Light vertical and horizontal stripes form a checkerboard pattern on a dark background and light spots mark the fins and dark areas of the body
      

      Save and exit the file.

      Now let’s add our code to textHelper.js. This module will contain some handy functions we’ll use to process the text file, making it easier to determine the most popular word. Open textHelper.js in your text editor:

      We’ll create three functions to process the data in sentences.txt. The first will be to read the file. Type the following into textHelper.js:

      debugger/textHelper.js

      const fs = require('fs');
      
      const readFile = () => {
        let data = fs.readFileSync('sentences.txt');
        let sentences = data.toString();
        return sentences;
      };
      

      First, we import the fs Node.js library so we can read files. We then create the readFile() function that uses readFileSync() to load the data from sentences.txt as a Buffer object and the toString() method to return it as a string.

      The next function we’ll add processes a string of text and flattens it to an array with its words. Add the following code into the editor:

      textHelper.js

      ...
      
      const getWords = (text) => {
        let allSentences = text.split('n');
        let flatSentence = allSentences.join(' ');
        let words = flatSentence.split(' ');
        words = words.map((word) => word.trim().toLowerCase());
        return words;
      };
      

      In this code, we are using the methods split(), join(), and map() to manipulate the string into an array of individual words. The function also lowercases each word to make counting easier.

      The last function needed returns the counts of different words in a string array. Add the last function like this:

      debugger/textHelper.js

      ...
      
      const countWords = (words) => {
        let map = {};
        words.forEach((word) => {
          if (word in map) {
            map[word] = 1;
          } else {
            map[word] += 1;
          }
        });
      
        return map;
      };
      

      Here we create a JavaScript object called map that has the words as its keys and their counts as the values. We loop through the array, adding one to a count of each word when it’s the current element of the loop. Let’s complete this module by exporting these functions, making them available to other modules:

      debugger/textHelper.js

      ...
      
      module.exports = { readFile, getWords, countWords };
      

      Save and exit.

      Our third and final file we’ll use for this exercise will use the textHelper.js module to find the most popular word in our text. Open index.js with your text editor:

      We begin our code by importing the textHelpers.js module:

      debugger/index.js

      const textHelper = require('./textHelper');
      

      Continue by creating a new array containing stop words:

      debugger/index.js

      ...
      
      const stopwords = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now', ''];
      

      Stop words are commonly used words in a language that we filter out before processing a text. We can use this to find more meaningful data than the result that the most popular word in English text is the or a.

      Continue by using the textHelper.js module functions to get a JavaScript object with words and their counts:

      debugger/index.js

      ...
      
      let sentences = textHelper.readFile();
      let words = textHelper.getWords(sentences);
      let wordCounts = textHelper.countWords(words);
      

      We can then complete this module by determining the words with the highest frequency. To do this, we’ll loop through each key of the object with the word counts and compare its count to the previously stored maximum. If the word’s count is higher, it becomes the new maximum.

      Add the following lines of code to compute the most popular word:

      debugger/index.js

      ...
      
      let max = -Infinity;
      let mostPopular="";
      
      Object.entries(wordCounts).forEach(([word, count]) => {
        if (stopwords.indexOf(word) === -1) {
          if (count > max) {
            max = count;
            mostPopular = word;
          }
        }
      });
      
      console.log(`The most popular word in the text is "${mostPopular}" with ${max} occurrences`);
      

      In this code, we are using Object.entries() to transform the key-value pairs in the wordCounts object into individual arrays, all of which are nested within a larger array. We then use the forEach() method and some conditional statements to test the count of each word and store the highest number.

      Save and exit the file.

      Let’s now run this file to see it in action. In your terminal enter this command:

      You will see the following output:

      Output

      The most popular word in the text is "whale" with 1 occurrences

      From reading the text, we can see that the answer is incorrect. A quick search in sentences.txt would highlight that the word whale appears more than once.

      We have quite a few functions that can cause this error: We may not be reading the entire file, or we may not be processing the text into the array and JavaScript object correctly. Our algorithm for finding the maximum word could also be incorrect. The best way to figure out what’s wrong is to use the debugger.

      Even without a large codebase, we don’t want to spend time stepping through each line of code to observe when things change. Instead, we can use breakpoints to go to those key moments before the function returns and observe the output.

      Let’s add breakpoints in each function in the textHelper.js module. To do so, we need to add the keyword debugger into our code.

      Open the textHelper.js file in the text editor. We’ll be using nano once again:

      First, we’ll add the breakpoint to the readFile() function like this:

      debugger/textHelper.js

      ...
      
      const readFile = () => {
        let data = fs.readFileSync('sentences.txt');
        let sentences = data.toString();
        debugger;
        return sentences;
      };
      
      ...
      

      Next, we’ll add another breakpoint to the getWords() function:

      debugger/textHelper.js

      ...
      
      const getWords = (text) => {
        let allSentences = text.split('n');
        let flatSentence = allSentences.join(' ');
        let words = flatSentence.split(' ');
        words = words.map((word) => word.trim().toLowerCase());
        debugger;
        return words;
      };
      
      ...
      

      Finally, we’ll add a breakpoint to the countWords() function:

      debugger/textHelper.js

      ...
      
      const countWords = (words) => {
        let map = {};
        words.forEach((word) => {
          if (word in map) {
            map[word] = 1;
          } else {
            map[word] += 1;
          }
        });
      
        debugger;
        return map;
      };
      
      ...
      

      Save and exit textHelper.js.

      Let’s begin the debugging process. Although the breakpoints are in textHelpers.js, we are debugging the main point of entry of our application: index.js. Start a debugging session by entering the following command in your shell:

      After entering the command, we’ll be greeted with this output:

      Output

      < Debugger listening on ws://127.0.0.1:9229/b2d3ce0e-3a64-4836-bdbf-84b6083d6d30 < For help, see: https://nodejs.org/en/docs/inspector < Debugger attached. Break on start in index.js:1 > 1 const textHelper = require('./textHelper'); 2 3 const stopwords = ['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers', 'herself', 'it', 'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what', 'which', 'who', 'whom', 'this', 'that', 'these', 'those', 'am', 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having', 'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or', 'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about', 'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under', 'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some', 'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very', 's', 't', 'can', 'will', 'just', 'don', 'should', 'now', ''];

      This time, enter c into the interactive debugger. As a reminder, c is short for continue. This jumps the debugger to the next breakpoint in the code. After pressing c and typing ENTER, you will see this in your console:

      Output

      break in textHelper.js:6 4 let data = fs.readFileSync('sentences.txt'); 5 let sentences = data.toString(); > 6 debugger; 7 return sentences; 8 };

      We’ve now saved some debugging time by going directly to our breakpoint.

      In this function, we want to be sure that all the text in the file is being returned. Add a watcher for the sentences variable so we can see what’s being returned:

      Press n to move to the next line of code so we can observe what’s in sentences. You will see the following output:

      Output

      break in textHelper.js:7 Watchers: 0: sentences="Whale shark Rhincodon typus gigantic but harmless shark family Rhincodontidae that is the largest living fishn" + 'Whale sharks are found in marine environments worldwide but mainly in tropical oceansn' + 'They make up the only species of the genus Rhincodon and are classified within the order Orectolobiformes a group containing the carpet sharksn' + 'The whale shark is enormous and reportedly capable of reaching a maximum length of about 18 metres 59 feetn' + 'Most specimens that have been studied however weighed about 15 tons about 14 metric tons and averaged about 12 metres 39 feet in lengthn' + 'The body coloration is distinctiven' + 'Light vertical and horizontal stripes form a checkerboard pattern on a dark background and light spots mark the fins and dark areas of the bodyn' 5 let sentences = data.toString(); 6 debugger; > 7 return sentences; 8 }; 9

      It seems that we aren’t having any problems reading the file; the problem must lie elsewhere in our code. Let’s move to the next breakpoint by pressing c once again. When you do, you’ll see this output:

      Output

      break in textHelper.js:15 Watchers: 0: sentences = ReferenceError: sentences is not defined at eval (eval at getWords (your_file_path/debugger/textHelper.js:15:3), <anonymous>:1:1) at Object.getWords (your_file_path/debugger/textHelper.js:15:3) at Object.<anonymous> (your_file_path/debugger/index.js:7:24) at Module._compile (internal/modules/cjs/loader.js:1125:14) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1167:10) at Module.load (internal/modules/cjs/loader.js:983:32) at Function.Module._load (internal/modules/cjs/loader.js:891:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:71:12) at internal/main/run_main_module.js:17:47 13 let words = flatSentence.split(' '); 14 words = words.map((word) => word.trim().toLowerCase()); >15 debugger; 16 return words; 17 };

      We get this error message because we set up a watcher for the sentences variable, but that variable does not exist in our current function scope. A watcher lasts for the entire debugging session, so as long as we keep watching sentences where it’s not defined, we’ll continue to see this error.

      We can stop watching variables with the unwatch() command. Let’s unwatch sentences so we no longer have to see this error message every time the debugger prints its output. In the interactive prompt, enter this command:

      The debugger does not output anything when you unwatch a variable.

      Back in the getWords() function, we want to be sure that we are returning a list of words that are taken from the text we loaded earlier. Let’s watch the value of the words variable:

      Then enter n to go to the next line of the debugger, so we can see what’s being stored in words. The debugger will show the following:

      Output

      break in textHelper.js:16 Watchers: 0: words = [ 'whale', 'shark', 'rhincodon', 'typus', 'gigantic', 'but', 'harmless', ... 'metres', '39', 'feet', 'in', 'length', '', 'the', 'body', 'coloration', ... ] 14 words = words.map((word) => word.trim().toLowerCase()); 15 debugger; >16 return words; 17 }; 18

      The debugger does not print out the entire array as it’s quite long and would make the output harder to read. However, the output meets our expectations of what should be stored: the text from sentences split into lowercase strings. It seems that getWords() is functioning correctly.

      Let’s move on to observe the countWords() function. First, unwatch the words array so we don’t cause any debugger errors when we are at the next breakpoint. In the command prompt, enter this:

      Next, enter c in the prompt. At our last breakpoint, we will see this in the shell:

      Output

      break in textHelper.js:29 27 }); 28 >29 debugger; 30 return map; 31 };

      In this function, we want to be sure that the map variable correctly contains the count of each word from our sentences. First, let’s tell the debugger to watch the map variable:

      Press n to move to the next line. The debugger will then display this:

      Output

      break in textHelper.js:30 Watchers: 0: map = { 12: NaN, 14: NaN, 15: NaN, 18: NaN, 39: NaN, 59: NaN, whale: 1, shark: 1, rhincodon: 1, typus: NaN, gigantic: NaN, ... } 28 29 debugger; >30 return map; 31 }; 32

      That does not look correct. It seems as though the method for counting words is producing erroneous results. We don’t know why those values are being entered, so our next step is to debug what’s happening in the loop used on the words array. To do this, we need to make some changes to where we place our breakpoint.

      First, exit the debug console:

      Open textHelper.js in your text editor so we can edit the breakpoints:

      First, knowing that readFile() and getWords() are working, we will remove their breakpoints. We then want to remove the breakpoint in countWords() from the end of the function, and add two new breakpoints to the beginning and end of the forEach() block.

      Edit textHelper.js so it looks like this:

      debugger/textHelper.js

      ...
      
      const readFile = () => {
        let data = fs.readFileSync('sentences.txt');
        let sentences = data.toString();
        return sentences;
      };
      
      const getWords = (text) => {
        let allSentences = text.split('n');
        let flatSentence = allSentences.join(' ');
        let words = flatSentence.split(' ');
        words = words.map((word) => word.trim().toLowerCase());
        return words;
      };
      
      const countWords = (words) => {
        let map = {};
        words.forEach((word) => {
          debugger;
          if (word in map) {
            map[word] = 1;
          } else {
            map[word] += 1;
          }
          debugger;
        });
      
        return map;
      };
      
      ...
      

      Save and exit nano with CTRL+X.

      Let’s start the debugger again with this command:

      To get insight into what’s happening, we want to debug a few things in the loop. First, let’s set up a watcher for word, the argument used in the forEach() loop containing the string that the loop is currently looking at. In the debug prompt, enter this:

      So far, we have only watched variables. But watches are not limited to variables. We can watch any valid JavaScript expression that’s used in our code.

      In practical terms, we can add a watcher for the condition word in map, which determines how we count numbers. In the debug prompt, create this watcher:

      Let’s also add a watcher for the value that’s being modified in the map variable:

      Watchers can even be expressions that aren’t used in our code but could be evaluated with the code we have. Let’s see how this works by adding a watcher for the length of the word variable:

      Now that we’ve set up all our watchers, let’s enter c into the debugger prompt so we can see how the first element in the loop of countWords() is evaluated. The debugger will print this output:

      Output

      break in textHelper.js:20 Watchers: 0: word = 'whale' 1: word in map = false 2: map[word] = undefined 3: word.length = 5 18 let map = {}; 19 words.forEach((word) => { >20 debugger; 21 if (word in map) { 22 map[word] = 1;

      The first word in the loop is whale. At this point, the map object has no key with whale as its empty. Following from that, when looking up whale in map, we get undefined. Lastly, the length of whale is 5. That does not help us debug the problem, but it does validate that we can watch any expression that could be evaluated with the code while debugging.

      Press c once more to see what’s changed by the end of the loop. The debugger will show this:

      Output

      break in textHelper.js:26 Watchers: 0: word = 'whale' 1: word in map = true 2: map[word] = NaN 3: word.length = 5 24 map[word] += 1; 25 } >26 debugger; 27 }); 28

      At the end of the loop, word in map is now true as the map variable contains a whale key. The value of map for the whale key is NaN, which highlights our problem. The if statement in countWords() is meant to set a word’s count to one if it’s new, and add one if it existed already.

      The culprit is the if statement’s condition. We should set map[word] to 1 if the word is not found in map. Right now, we are adding one if word is found. At the beginning of the loop, map["whale"] is undefined. In JavaScript, undefined + 1 evaluates to NaN—not a number.

      The fix for this would be to change the condition of the if statement from (word in map) to (!(word in map)), using the ! operator to test if word is not in map. Let’s make that change in the countWords() function to see what happens.

      First, exit the debugger:

      Now open the textHelper.js file with your text editor:

      Modify the countWords() function as follows:

      debugger/textHelper.js

      ...
      
      const countWords = (words) => {
        let map = {};
        words.forEach((word) => {
          if (!(word in map)) {
            map[word] = 1;
          } else {
            map[word] += 1;
          }
        });
      
        return map;
      };
      
      ...
      

      Save and close the editor.

      Now let’s run this file without a debugger. In the terminal, enter this:

      The script will output the following sentence:

      Output

      The most popular word in the text is "whale" with 3 occurrences

      This output seems a lot more likely than what we received before. With the debugger, we figured out which function caused the problem and which functions did not.

      We’ve debugged two different Node.js programs with the built-in CLI debugger. We are now able to set up breakpoints with the debugger keyword and create various watchers to observe changes in internal state. But sometimes, code can be more effectively debugged from a GUI application.

      In the next section, we’ll use the debugger in Google Chrome’s DevTools. We’ll start the debugger in Node.js, navigate to a dedicated debugging page in Google Chrome, and set up breakpoints and watchers using the GUI.

      Chrome DevTools is a popular choice for debugging Node.js in a web browser. As Node.js uses the same V8 JavaScript engine that Chrome uses, the debugging experience is more integrated than with other debuggers.

      For this exercise, we’ll create a new Node.js application that runs an HTTP server and returns a JSON response. We’ll then use the debugger to set up breakpoints and gain deeper insight into what response is being generated for the request.

      Let’s create a new file called server.js that will store our server code. Open the file in the text editor:

      This application will return a JSON with a Hello World greeting. It will have an array of messages in different languages. When a request is received, it will randomly pick a greeting and return it in a JSON body.

      This application will run on our localhost server on port :8000. If you’d like to learn more about creating HTTP servers with Node.js, read our guide on How To Create a Web Server in Node.js with the HTTP Module.

      Type the following code into the text editor:

      debugger/server.js

      const http = require("http");
      
      const host="localhost";
      const port = 8000;
      
      const greetings = ["Hello world", "Hola mundo", "Bonjour le monde", "Hallo Welt", "Salve mundi"];
      
      const getGreeting = function () {
        let greeting = greetings[Math.floor(Math.random() * greetings.length)];
        return greeting
      }
      

      We begin by importing the http module, which is needed to create an HTTP server. We then set up the host and port variables that we will use later to run the server. The greetings array contains all the possible greetings our server can return. The getGreeting() function randomly selects a greeting and returns it.

      Let’s add the request listener that processes HTTP requests and add code to run our server. Continue editing the Node.js module by typing the following:

      debugger/server.js

      ...
      
      const requestListener = function (req, res) {
        let message = getGreeting();
        res.setHeader("Content-Type", "application/json");
        res.writeHead(200);
        res.end(`{"message": "${message}"}`);
      };
      
      const server = http.createServer(requestListener);
      server.listen(port, host, () => {
        console.log(`Server is running on http://${host}:${port}`);
      });
      

      Our server is now ready for use, so let’s set up the Chrome debugger.

      We can start the Chrome debugger with the following command:

      Note: Keep in mind the difference between the CLI debugger and the Chrome debugger commands. When using the CLI you use inspect. When using Chrome you use --inspect.

      After starting the debugger, you’ll find the following output:

      Output

      Debugger listening on ws://127.0.0.1:9229/996cfbaf-78ca-4ebd-9fd5-893888efe8b3 For help, see: https://nodejs.org/en/docs/inspector Server is running on http://localhost:8000

      Now open Google Chrome or Chromium and enter chrome://inspect in the address bar. Microsoft Edge also uses the V8 JavaScript engine, and can thus use the same debugger. If you are using Microsoft Edge, navigate to edge://inspect.

      After navigating to the URL, you will see the following page:

      Screenshot of Google Chome's "inspect" page

      Under the Devices header, click the Open dedicated DevTools for Node button. A new window will pop up:

      Screenshot of debug window

      We’re now able to debug our Node.js code with Chrome. Navigate to the Sources tab if not already there. On the left-hand side, expand the file tree and select server.js:

      Screenshot of debugger window's Sources tab

      Let’s add a breakpoint to our code. We want to stop when the server has selected a greeting and is about to return it. Click on the line number 10 in the debug console. A red dot will appear next to the number and the right-hand panel will indicate a new breakpoint was added:

      Screenshot of adding a breakpoint in the Chrome debugger

      Now let’s add a watch expression. On the right panel, click the arrow next to the Watch header to open the watch words list, then click +. Enter greeting and press ENTER so that we can observe its value when processing a request.

      Next, let’s debug our code. Open a new browser window and navigate to http://localhost:8000—the address the Node.js server is running on. When pressing ENTER, we will not immediately get a response. Instead, the debug window will pop up once again. If it does not immediately come into focus, navigate to the debug window to see this:

      Screenshot of the program's execution paused in Chrome

      The debugger pauses the server’s response where we set our breakpoint. The variables that we watch are updated in the right panel and also in the line of code that created it.

      Let’s complete the response’s execution by pressing the continue button at the right panel, right above Paused on breakpoint. When the response is complete, you will see a successful JSON response in the browser window used to speak with the Node.js server:

      {"message": "Hello world"}
      

      In this way, Chrome DevTools does not require changes to the code to add breakpoints. If you prefer to use graphical applications over the command line to debug, the Chrome DevTools are more suitable for you.

      Conclusion

      In this article, we debugged sample Node.js applications by setting up watchers to observe the state of our application, and then by adding breakpoints to allow us to pause execution at various points in our program’s execution. We accomplished this using both the built-in CLI debugger and Google Chrome’s DevTools.

      Many Node.js developers log to the console to debug their code. While this is useful, it’s not as flexible as being able to pause execution and watch various state changes. Because of this, using debugging tools is often more efficient, and will save time over the course of developing a project.

      To learn more about these debugging tools, you can read the Node.js documentation or the Chrome DevTools documentation. If you’d like to continue learning Node.js, you can return to the How To Code in Node.js series, or browse programming projects and setups on our Node topic page.



      Source link