The first method is to check the browser's error console. Here, you can find the type of error, the exact line of code, and other useful information that helps you locate and fix the issue.
If we input a duration less than 1 (e.g., 0, -1), the app crashes and shows the following error in the console:
Uncaught TypeError: Cannot read properties of undefined (reading 'valueEndOfYear') at Results (Results.jsx:12:16)
This error tells us exactly where to look in our code. On line 12 of Results.jsx, we have a constant that retrieves data from another function:
const initialInvestment =
results[0].valueEndOfYear -
results[0].interest -
results[0].annualInvestment;
The results variable is populated by calling:
const results = [];
calculateInvestmentResults(input, results);
When we take a look on calculateInvestmentResults, we see it contains a for loop that relies on duration as the iteration count:
for (let i = 0; i < duration; i++) {
//some logic
}
Since duration is 0, this loop never executes, leaving results empty, which leads to the error.
Tip
The console is your first go-to for quick details on any unexpected error.
To handle this, we have a couple of options:
- Add validation in calculateInvestmentResults to check if duration is valid. If it isn’t, we could return a default value or a specific error message. However, this would add extra logic to the function, so it may not be the best approach.
- Show a user-friendly message in the UI when duration is invalid. This approach informs the user about the error and suggests how to correct it, making it a better option.
To implement this, we’ll add a condition in Results to catch the error and display a helpful message to guide the user:
if (results.length === 0) {
return <p className="center">Invalid input data provided</p>;
}
Another way is to leverage the browser’s developer tools, especially the Source tab. Here, you can see your entire project structure and navigate between files. With breakpoints, you can monitor values and follow the flow of your code, checking if data is correctly passed between components for example.
If we change the value of the initial investment or annual investment, we notice that the table displays unusually large numbers. But what’s causing this? The console shows no errors, so how can we identify the issue?
When we reload the page, all data in the table appears correct, indicating that the investment calculation function and the Results component are working as expected. However, when we adjust the initial or annual investment values, the table breaks.
Upon inspecting the UserInput component, we find that it’s not managing state directly. Instead, it forwards props from App.jsx:
export default function UserInput({ onChange, userInput })
In App.jsx, we see that handleChange is used to update values:
<UserInput userInput={userInput} onChange={handleChange} />
function handleChange(inputIdentifier, newValue) {
setUserInput((prevUserInput) => {
return {
...prevUserInput,
[inputIdentifier]: newValue,
};
});
}
To troubleshoot further, let’s use the Source tab in the browser’s developer tools. We can place a breakpoint to pause the app at the exact point where values change, allowing us to inspect the variables in real time.
Tip
Place breakpoints strategically to track data flow.
With the breakpoint in place, when we modify a value, we notice that the updated value is a string instead of a number.
Note
In JavaScript, values are treated as strings when combined with other strings, leading to concatenation instead of numeric addition (e.g., 202 + '101' becomes '202101')
To fix this, we need to convert the incoming string values to numbers within the function that handles changes. We can do this by adding a + sign before newValue:
[inputIdentifier]: +newValue
With this adjustment, the table now displays the correct information, and the error is fixed.
Use extensions like React Developer Tools. This tool allows you to see your entire component structure. By clicking on a component, you can inspect its state, props, and functions, and even modify values in real-time to observe your app’s response.
Tip
The Components view in React DevTools is ideal for analyzing each component's state and props throughout your app.
Another tool is Strict Mode, a component provided by the React library that helps identify potential issues in your app's logic. To use it, you simply need to wrap your application (or specific parts of it) with . This mode highlights unexpected behaviors, unsafe lifecycle methods, and deprecated features, making it easier to detect logic-related issues that could affect your app's stability.
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);
If we declare results outside the component like this:
const results = [];
export default function Results({ input }) {
// some code...
}
Because we’ve wrapped our app in Strict Mode, React renders components twice in development mode to help identify potential issues. This double rendering reveals a problem: the results table repopulates twice, not due to Strict Mode itself, but because results isn’t re-initialized to an empty array on each render. Without Strict Mode, this issue might remain hidden until another action or code change triggers the error. Strict Mode, therefore, helps us catch these types of issues early.
Tip
Enabling Strict Mode is especially useful in development, as it helps catch subtle bugs and ensures that your code follows React's best practices.
To fix this, simply move the const results = [] declaration inside the component:
export default function Results({ input }) {
const results = [];
// some code...
}
🐸 This project is a practice exercise I learned from the Academind's React Course 🐸