How to Fix Correctly Handle JSON Response from PHP During Fetch Requests

When working with fetch requests using PHP, it’s easy to run into issues with the response handling. One common error that developers encounter is the TypeError: Cannot read properties of undefined (reading 'message'). This typically occurs when the promise chain isn’t correctly processing the response from the server, leading to the inability to access important properties like message and success from the returned JSON object.

I’ll explain the cause of this error, provide a solution, and also suggest how to make the functionality more robust for real-world applications.

The Error Code

Here’s the basic structure of the fetch request that’s causing the issue:

("../contact.php", {
method: "POST",
body: formData,
})
.then((response) => {
response.json(); // Potential Issue
})
.then((data) => {
formErr.textContent = data.message;
if (data.success) {
document.getElementById("contactForm").reset();
grecaptcha.reset();
}
})
.catch((error) => {
formErr.textContent = "An error occurred: " + error.message;
});

At first glance, the code seems straightforward. However, the issue lies in the fact that the response.json() function is not being returned, which results in the data object being undefined in the second .then() block. Consequently, when the code tries to access data.message, it throws the error TypeError: Cannot read properties of undefined (reading 'message').

Error Analysis & Explanation:

The core problem is that the promise returned by response.json() isn’t being passed properly to the next .then() block. The .json() method is asynchronous, meaning it doesn’t immediately return the parsed JSON objectm it returns a promise that resolves to the object. Without returning this promise in the first .then(), the second .then() gets nothing, resulting in the data object being undefined.

To fix this, we need to return the result of response.json() in the first .then() block, ensuring the promise chain is correctly formed.

Correct the Code

Here’s the corrected version of your code:

("../contact.php", {
method: "POST",
body: formData,
})
.then((response) => response.json()) // Ensure that you return the promise here
.then((data) => {
formErr.textContent = data.message;
if (data.success) {
document.getElementById("contactForm").reset();
grecaptcha.reset();
}
})
.catch((error) => {
formErr.textContent = "An error occurred: " + error.message;
});

Key Explanation:

  • Fixing the return statement: The key fix here is the use of return response.json() in the first .then() block. This ensures that the parsed JSON object is passed on to the next .then() block.
  • Understanding the error: The error happened because the promise returned by response.json() was not properly passed to the next .then(). When data.message was accessed in the second .then(), data was undefined.

Advanced Improvements and Additional Practice Functionality

Now that we’ve solved the core problem, let’s take this functionality a step further by adding a few enhancements for better error handling and response validation.

Check for Valid JSON:

In some cases, the response might not be valid JSON, leading to parsing errors. It’s good practice to check the response before attempting to parse it.

Handle HTTP Status Codes:

Sometimes, the server might return a response with a non-200 HTTP status code, even if the body contains JSON. You should check the status code before proceeding with JSON parsing.

Enhanced Code Example:

("../contact.php", {
method: "POST",
body: formData,
})
.then((response) => {
// Check if the response status is OK
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json(); // Return the parsed JSON if response is valid
})
.then((data) => {
if (data.success) {
formErr.textContent = data.message;
document.getElementById("contactForm").reset();
grecaptcha.reset();
} else {
formErr.textContent = "Something went wrong: " + data.message;
}
})
.catch((error) => {
// Handle errors more thoroughly
console.error("Error:", error);
formErr.textContent = "An error occurred: " + error.message;
});

Explanation of Enhancements:

  • HTTP Status Check:
    • I added a check to ensure the HTTP response status is OK (status code 200). If it’s not, an error is thrown with the status code, making it easier to diagnose server-side issues.
  • Comprehensive Error Handling:
    • The .catch() block now logs the error to the console in addition to updating the UI. This gives more visibility into the exact problem, which is useful for debugging.
  • Improved User Feedback:
    • The feedback in the UI (formErr.textContent) now differentiates between a successful response (data.success) and one that failed, displaying appropriate error messages based on the result.
  • Check for Valid JSON:
    • While this is assumed to be correct in most cases, for a more robust implementation, you could add further checks (e.g., using response.text() first, then parsing it manually). This ensures that you don’t encounter errors if the server returns non-JSON data or malformed JSON.

Final Thoughts:

In this blog post, we explored the common issue of parsing JSON responses in JavaScript when making fetch requests. The error TypeError: Cannot read properties of undefined (reading 'message') typically happens when the promise chain is broken due to the improper handling of asynchronous code.

The solution is simple: always return the promise from response.json() in the first .then() block. From there, we made the functionality more robust by adding error handling for HTTP status codes and improving feedback for the user.

Related blog posts