Fahad Malik
Fahad Malik

Reputation: 47

Cannot read property 'length' of undefined...Although the object is defined

First I have the following code section:

export default class Recipe {
  constructor(recID) {
    this.RecipeID = recID;
  }

  async GetRecipe() {
    try {
      let Result = await Axios(
        `https://forkify-api.herokuapp.com/api/get?rId=${this.RecipeID}`
      );
      this.Tilte = Result.data.recipe.title;
      this.Author = Result.data.recipe.publisher;
      this.Image = Result.data.recipe.image_url;
      this.Url = Result.data.recipe.source_url;
      this.Ingredients = Result.data.recipe.ingredients;
      this.PublisherUrl = Result.data.recipe.publisher_url;
      this.Rank = Result.data.recipe.social_rank;
    } catch (error) {
      alert(error);
    }
  }

  CalculateTime() {
    try {
      this.Time = Math.ceil(this.Ingredients.length / 3) * 15; // error is here
    } catch (error) {
      console.log(this.RecipeID + ": Length Error->"+error);
    }
  }
}

Then I call the above code in a separate file like:

import Recipe from "./Recipe";

const RecipeController = async () => {
  const ID = window.location.hash.replace("#", "");

  if (ID) {
    AppState.Recipe = new Recipe(ID);

    try {
      await AppState.Recipe.GetRecipe();

      AppState.Recipe.CalculateTime();

      console.log(AppState.Recipe);
    } catch (error) {
      alert(error);
    }
  }
};

Now as shown in the following image, that I do get the response of the request & promised is resolved plus there are elements in the 'ingredients' array but sometimes I still get the error "cannot read property 'length' of undefined" when I call the 'CalculateTime()' although the array is now defined and sometimes I don't get any error & it works perfectly fine.Why this random behavior? Even the IDs in the JSON response & the error I logged match i.e. 47746. enter image description here

Upvotes: 1

Views: 202

Answers (3)

Evgenii Klepilin
Evgenii Klepilin

Reputation: 695

I would speculate that this "random" behavior can be related to asynchronous code. You need to ensure that the class has Ingredients in place before you calculate. I have a feeling that you should try changing your syntax to a Promise handling using .then() and .catch(), especially since you already use try/catch in your code. This approach will ensure proper resolution of Promise on axios request, and will eliminate "randomness", because you will have a better control over different stages of Promise processing.

let Result = await Axios(
        `https://forkify-api.herokuapp.com/api/get?rId=${this.RecipeID}`
       )
       .then((data) => {
           this.Tilte = data.data.recipe.title;
           this.Author = data.data.recipe.publisher;
           this.Image = data.data.recipe.image_url;
           this.Url = data.data.recipe.source_url;
           this.Ingredients = data.data.recipe.ingredients;
           this.PublisherUrl = data.data.recipe.publisher_url;
           this.Rank = data.data.recipe.social_rank;
           this.Ingerdients = data.data.recipe.ingredient;
       }
       .catch((err) => {
           console.log(err);
           return null;
       });

Upvotes: 0

CertainPerformance
CertainPerformance

Reputation: 370729

This is one reason why having too many try/catches can obscure the causes of errors, making debugging difficult. The problem can be reduced to the following:

class Recipe {
  constructor(recID) {
    this.RecipeID = recID;
  }
  async GetRecipe() {
    let Result = await fetch(
      `https://forkify-api.herokuapp.com/api/get?rId=47746`
    ).then(res => res.json());
    console.log(Result); // <----- look at this log
    this.Tilte = Result.data.recipe.title;
    // on the above line, the error is thrown
    // Cannot read property 'recipe' of undefined
  }
}
const r = new Recipe();
r.GetRecipe();

See the log: your Result object does not have a .data property, so referencing Result.data.recipe throws an error. Try Result.recipe instead:

class Recipe {
  constructor(recID) {
    this.RecipeID = recID;
  }
  async GetRecipe() {
    let Result = await fetch(
      `https://forkify-api.herokuapp.com/api/get?rId=47746`
    ).then(res => res.json());
    const { recipe } = Result;
    this.Tilte = recipe.title;
    this.Author = recipe.publisher;
    this.Image = recipe.image_url;
    this.Url = recipe.source_url;
    this.Ingredients = recipe.ingredients;
    this.PublisherUrl = recipe.publisher_url;
    this.Rank = recipe.social_rank;
  }

  CalculateTime() {
    this.Time = Math.ceil(this.Ingredients.length / 3) * 15; // error is here
    console.log('got time', this.Time);
  }
}
(async () => {
  const r = new Recipe();
  await r.GetRecipe();
  r.CalculateTime();
})();

Unless you can actually handle an error at a particular point, it's usually good to allow the error to percolate upwards to the caller, so that the caller can see that there was an error, and handle it if it can. Consider changing your original code so that RecipeController (and only RecipeController) can see errors and deal with them - you can remove the try/catches from the Recipe.

Upvotes: 1

Dvid Silva
Dvid Silva

Reputation: 1162

Are you sure some of the responses are not missing ingredients? And calculateTime is always called after getRecipe?

I would add a condition or fallback to prevent errors, as in.

 this.Time = Math.ceil((this.Ingredients || []).length / 3) * 15; 

Upvotes: 0

Related Questions