Beyond React console.log: Understanding debugging in React

Posted on March 31st, 2021

In debugging React (or any kind of JavaScript-based application) developers tend to rely on the standard console.log for insights. However, if you’re looking to detect and prevent bugs in React? There is more to it than just using console.log

React comes with a series of tools out of the box that can help you find and eliminate those pesky bugs in your application. Before we get to know them, let’s take a look at the types of bugs you are likely to come across.

4 Types of bugs in React

Debugging is a series of steps taken to remove a bug from an application. Bugs are things that break the code or make it work in a way that results in an unintended side effect.

In React, knowing what kind of bug you’re dealing with can help you select the right debugging process. There are four general categories of bugs that you’ll most likely encounter in a React app:

1. User interface bugs

A user interface bug in React occurs when something is not doing what we expect it to do, or when parts of the UI just don’t look quite right. The cause of this bug is often caused by one of the following things:

You’re not covering all the states

By design, React repaints the interface every time a change occurs. For example, a common one is that your form inputs don’t seem to be working quite right. The value displayed is not what you typed in. When this happens, it’s because you haven’t specifically called the individual set and get values.

 //setting up the state
  const [search, setSearch] = useState('');
 ...
 //the jsx for setting and using the state
        <input
        className="search-bar"
        type="text"
        value={search}
        onChange={updateSearch} />

It’s your CSS

If the placement of UI elements and its visual appearance doesn’t seem quite right? Then your CSS is most likely what is broken.

2. Logic bugs

A logical bug occurs when the application is not behaving the way you expect it to behave. Your data may not be rendered correctly or perhaps your interpolation just isn’t working. In this case, debugging becomes a process of narrowing down the origin of the issue so you can inspect and eliminate the bug accordingly.

3. Networking bugs

Networking bugs are often tricky because you’re dealing with a third-party service like an API. When a network times out, it’s usually because you’ve reached the API limits, your keys are invalid or you’re experiencing a CORs issue because the third-party hasn’t configured the accessibility to accept your requests.

This means your logic is generally correct but something on the other side is returning an error.

4. Feature regressions

A feature regression is when something used to work in the past but no longer does. This can be caused by several factors such as legacy code, conflicts with new code, newly introduced network bugs, or depreciated features that are no longer supported.

When this occurs, it is a matter of updating the code to fit with the current requirements, or refactoring it to ensure future and forward compatibility.

React console statements

When we try to debug something, we often just use console.log. However, there’s more to it than just console.log. console as an object, and .log is only one of its available methods. 

Here is a quick rundown on all the different console statements that you can use and when you should use them.

console.warn()

When using console.warn(), the log gets highlighted with a yellow background and the text becomes a mustard color.

The data attached to this log includes information such as where the console.warn() is, when it is invoked, and any callbacks that are attached to it.

console.error()

console.warn() gives a red warning highlight, as well as information such as the location where it is invoked, what kind of function the invoked is (eg. the function might be an async function), and any kind of callbacks the function is expected.

console.table()

When dealing with data, it can be quite hard to trace through reams of nested dropdowns. console.table() lets you format the data in a way that is easily digestible by presenting it as an actual data table. This can help speed up the debugging process by making the data easier to read.

console.trace()

console.trace() works in a similar fashion to error and warn, except the highlighting is not as dramatic.

It is presented with all the related information up to the nearest scope of its invoked space such as function name, type of function, and any kind of attached special features such as guards, and callbacks.

Creating React breakpoints

You can create debugging breakpoints in React through your browser by using the debugger; statement inside your code.

For example, you might have a problematic function but you’re not sure if it’s the one causing the issue. You can place debugger; in the code and when your React app runs, your browser will automatically stop the process for you to observe and decide if it is the issue.

This can be useful in conjunction with a console statement. This lets you programmatically pause the application and run it to the next breakpoint when you’re ready to continue.

Here is an example of how it can be used in your React app:

const [recipes, setRecipes] = useState([]);
  const [search, setSearch] = useState('');
  const [query, setQuery] = useState('chicken');
 ​
 ​
 ​
  useEffect( () =>{
    getRecipes();
    debugger;
  }, [query])
 ​
  const getRecipes = async () => {
    const response = await fetch(`https://api.edamam.com/search?q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`);
    const data = await response.json();
    setRecipes(data.hits);
    console.log(data.hits);
 ​
 ​
    debugger;
 ​
 ​
  }
 ​
  const updateSearch = e => {
    setSearch(e.target.value);
    console.log(search);
 ​
    debugger;
  }
 ​
  const getSearch = e => {
    e.preventDefault();
    setQuery(search);
    setSearch('');
  }

There are three different debugger stop points in the code above. This means that your application will stop when the debugger; is invoked and will not proceed to the next breakpoint until you say so.

Using React Developer Tools

In addition to using console statements and debugger;, you can also download React Developer Tools to help you understand how your app is working and what is happening behind the scenes. Once installed, you’ll have access to a React tab inside your browser console.

The React DevTools lets you inspect the React element tree and see how the structure is constructed, including any properties passed in and any related hooks involved.

React DevTools is available as a browser extension for Chrome, Firefox, and Microsoft Edge. Here is a Chrome screenshot of what it looks like:

Using an error boundary

An error boundary is a visual logging tool that lets you ringfence issues with ‘something went wrong’ messages for your users. This can also be useful for developers too. That is because it lets you log the issue out in the console and silently fails that particular widget without impacting the rest of the app’s functionality.

The error boundary is used to wrap around a particular widget. While it can be a fallback for critical widgets in the production stage, you can also use it in development as a debugging console output tool.

To use an error boundary, you’ll need to set it up as a class like this:

 class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
 ​
  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }
 ​
  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }
 ​
  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }
 ​
    return this.props.children;
  }
 }

Once you’ve done that, you can now use it to wrap around your problem widget like this:

 <ErrorBoundary>
  <YourWidget />
 </ErrorBoundary>

The console debugging techniques listed here should cover most of your console errors and UI problems you will encounter with React.

While there is no one-size-fits-all fix for your bugs, keeping calm and methodologically going through the issue via a process of elimination will often lead you to arrive at your solution eventually.

The fundamentals of C++

Posted on March 24th, 2021

C++ has been around a long time — since the 70s to be more precise. C++ is especially popular in the game industry because it gives the developer high levels of control over performance, memory, and resource allocations.

The C++ language is part of the C-language family, which means that if you learn C++, there’s a high chance of transferring that knowledge over to niche programming projects that uses C.

This article will give you an introduction to the fundamentals of C++ to help you on your learning journey. If you already know a programming language, most of the concepts introduced here will be familiar. This is because C++ is a partial object-oriented language. So if you’re already familiar with Java and C#? C++ should be a breeze.

STM32F4 – Object-oriented Programming with Embedded Systems (C++ /w STL) |  Iztok's HW/SW Sandbox

Writing C++ Statements

A statement is the foundation and building block of all programming. It’s like a code-based sentence that has a specific instruction that causes the program to perform an action.

In C++, a statement often ends in a semicolon. Here is an example of a statement:

#include <iostream> ​ int main()
{
std::cout << "Meow! Meow!";
return 0;
}

Lines 5 and 6 are statements. If you miss out the ;, you will get a syntax error message. syntax can be viewed as the grammar rules for programming. If your C++ syntax doesn’t follow the established rules, it will be considered invalid.

main() function acts as an entry point for our application. Every C++ program will have one main() function. Programs work in a linear manner. It is a set of instructions executed in turn. If there are multiple main(), you will get a console error because the program doesn’t know which main() is the true starting point.

Commenting In C++

Commenting in C++ is the same as other major programming languages. // represents a single-line comment. This includes text that may wrap but is technically on the same line because it hasn’t been escaped.

For multi-lined comments are anything that sits in between the /* and */ pair.

For example:

std::cout << "Hello there!n" //std::count is part of the iostream library
​
std::cout << "Just printing another line.n"
​
/* This is a
multi-line
comment. */
​
/* For formatting purposes,
  * it is convention to have a space and * preceding
  * to give your multi-line text alignment
  */

Using multi-line comments inside another multi-line comment won’t work and will cause syntax errors because how the * and * are paired up.

The general reason behind using a comment is usually to describe what the library, function, or program does. The purpose of this is to act as a guide to new developers dealing with the code. It is a way to help document your code and make it easier to understand its purpose without needing to go diving into the actual code.

Writing a good comment should inform the developer what the code after it is supposed to represent. This means writing description rich comments.

For example, here is a bad comment:

// Set height range to 0
height = 0;

This doesn’t tell the developer much except what he can already see. Here is an example of a better description.

// The block's height is set to 0 because the game has just started.
height = 0;

In the above example, the comment is description rich and gives the developer context.

Variables, Assignments and Initialization

In C++, we can access saved variables in memory via an object. A named object is called a variable and the name of that variable is called an identifier. Declaring a variable for the first time (i.e. when you create the variable) is called writing a definition. Once the definition is written, it is called instantiation — that is the moment when the object is first created and assigned a memory space.

Here is an example of a variable definition being instantiated.

int x;

In the above example, the data type is int. There are 7 primary or primitive data types in C++. They are:

  • int integer
  • char character
  • bool boolean
  • float floating point
  • double double floating point
  • void valueless or void
  • wchar_t wide character

You can also declare multiple variables at once using ,. For example:

int x, y;

Is the same as writing the following:

int x;
int y;

The declared values need to have the same type, or else you’d end up with a syntax error.

To assign a value to a variable, you can do so using the = operator. Once you’ve already declared and initialized the value with a type, you don’t need to do it again.

Here is an example of a variable assignment:

int x;
x = 5;
​
int y = 7; //this will also work

Do note that = is an assignment while == is an equality operator used for testing values. This method of using = to assign value is called a copy initialization.

An alternative to this is to perform a direct initialization using a pair of parenthesis ().

int x(5); //example of direct initialization

To initialize multiple variables at once, you can follow the , pattern with assignments. For example:

int x = 5, y = 7;
int a(1), b(2);

Uninitialized variables will not be set as empty but will take on whatever garbage value that previously existed in the memory allocated spot. For example, if you print out an uninitialized value, you’ll get some strange return.

#include <iostream>
​
int main()
{
  int x;
  std::cout << x;
  return 0;
}

The output will be whatever used to exist where x is currently allocated. No value returned will be the same.

iostream: cout, cin, and endl

In the first example for this piece, we encountered #include <iostream>. This is the C++ way of importing libraries for you to access their preconfigured functionalities, settings, and methods. In this case, the iostream refers to the input/output library — or io library.

Inside the iostream library, there are several predefined variables that we can use. std::cout is something that you can use to send data to the console as a text output. cout, as strange as it initially looks, actually stands for “character output”.

Here is an example of std::cout in action:

#include <iostream>
​
int main()
{
    std::cout << "How now brown cow!";
    return 0;
}

<< is called an insertion operator and allows you to insert your value into the std::cout function.

std::cout can take several printed and calculated parameters, in addition to variables. For example:

#include <iostream>
​
int main()
{
    int x('hello');
    std::cout << x;
    return 0;
}

hello will be printed in the console as a result. You can have more than one thing printed on the same line. Just add another insertion operator << to create the link. For example:

#include <iostream>
​
int main()
{
    int x('hello');
    int y(' brown cow');
    std::cout << x << y;
    return 0;
}

This will result in the output hello brown cow.

It is good to note that if you have several std::cout, it will still print everything on the same line. std::endl acts as a line break for your console output. Here is how you can use it:

#include <iostream>
​
int main()
{
    int x('hello');
    int y(' brown cow');
    std::cout << x << y << std::endl;
    std::cout << x << y << std::endl;
    return 0;
}

Without std::endl, you’d end up with hello brown cowhello brown cow as a single line.

Lastly for this section, there is std::cin — which stands for character input. This lets your console become a little bit more interactive by allowing you to assign user input to a variable. For example:

#include <iostream>
​
int main()
{
  std::cout << "What's your name? ";
    int name{ };
    std::cin >> name;
    std::cout << "Hello, " << name << 'n';
    return 0;
}

Note the direction of >> for std::cin. This is because >> is an extraction operator and the input is stored in the variable referenced. n is also another way to create a line break in the console output.

Where To From Here?

That’s the initial basic syntax for C++. If you already know a programming language like Java, moving forward works on almost the same theoretical foundations. The only major difference that you might need to explore further is how advanced syntax is structured.

If you’re new to programming in general and have a foundational understanding? The next steps from here would be exploring how arrays, functions and files work, object scope, conversions, bit manipulation, and control flows.

Just a list of good programming memes