1

I am still new to React and have been doing small projects lately to get better. I am currently working on a nutrition webpage that sets calorie goals and obtains foods from an API. This project consists of 2 components FoodItem and Main.

The Main component calculates calories and displays the search results from the API. Here lies the problem. When the search bar first receives a name, it displays nothing. However, it displays the intended search results after a backspace (deleting one letter from the word). This is seen in the screenshots.

Full word:

FullWord

After deleting one letter:

After Deleting one letter

Here is the function responsible for displaying the Search Results:

updateResult(name) {
  console.log(name);
  if (name == "") {
    this.setState({
      foodResult: []
    })
    return;
  }
  let result = [];
  let url = 'https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=key&ingr=' + name;
  fetch(url)
    .then(
      function(response) {
        return response.json();
      }
    ).then(function(jsonData) {
      for (let i = 0; i < jsonData.hints.length; i++) {
        foods.push({
          name: jsonData.hints[i].food.label,
          calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL)
        })
      }
    })
  console.log(foods);
  foods = removeDuplicates(foods);

  for (let i = 0; i < foods.length; i++) {
    if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
      result.push(
        <FoodItem name ={foods[i].name} calories ={foods[i].calories} updateFoods = {this.displayEatenFoods} isEaten = {false} checkItem = {this.checkItem}/>)
      }
    }
    console.log(result);
    this.setState({
      foodResult: result
    });
  }

Full code:

import React from "react";
import ReactDOM from "react-dom";
//////////////////

let foods = [];
function removeDuplicates(arr) {
  var unique = [];
  for (let i = 0; i < arr.length; i++) {
    let current = arr[i].name;
    let add = true;
    for (let i = 0; i < unique.length; i++) {
      if (current == unique[i].name) add = false;
    }
    if (add) unique.push(arr[i]);
  }
  return unique;
}
///////////////////
class FoodItem extends React.Component {
  constructor(props) {
    super(props);
    this.state = { addDone: false, disable: false };
    this.addEaten = this.addEaten.bind(this);
  }

  addEaten() {
    if (this.props.updateFoods(this.props.name))
      this.setState({ addDone: false, disable: true });
    else this.setState({ addDone: true });
  }

  render() {
    if (this.props.isEaten) {
      return (
        <div>
          {this.props.name}
          &ensp; Calories :{this.props.calories}
        </div>
      );
    }
    if (!this.state.addDone) {
      return (
        <div>
          {this.props.name}
          &ensp; Calories :{this.props.calories}
          &ensp;
          <button primary onClick={this.addEaten} disabled={this.state.disable}>
            Eat
          </button>
        </div>
      );
    }
    return null;
  }
}

class Main extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      goal: " ",
      remaining: " ",
      goalEntered: false,
      foodSearch: "",
      foodResult: [],
      EatensFoods: [],
      allowance: " ",
      calories: ""
    };
    this.setGoal = this.setGoal.bind(this);
    this.changeGoal = this.changeGoal.bind(this);
    this.changeFoodSearch = this.changeFoodSearch.bind(this);
    this.displayEatenFoods = this.displayEatenFoods.bind(this);
    this.checkItem = this.checkItem.bind(this);
    this.changeCalorieSearch = this.changeCalorieSearch.bind(this);
  }

  changeGoal(event) {
    this.setState({ goal: event.target.value });
  }

  setGoal(event) {
    this.setState({ goalEntered: true, remaining: this.state.goal });
    event.preventDefault();
  }

  changeFoodSearch(event) {
    this.setState({ foodSearch: event.target.value });
    this.updateResult(event.target.value);
  }

  changeCalorieSearch(event) {
    this.setState({ calories: event.target.value });
  }

  updateResult(name) {
    console.log(name);
    if (name == "") {
      this.setState({ foodResult: [] });
      return;
    }
    let result = [];
    let url =
      "https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=key&ingr=" +
      name;
    fetch(url)
      .then(function(response) {
        return response.json();
      })
      .then(function(jsonData) {
        for (let i = 0; i < jsonData.hints.length; i++) {
          foods.push({
            name: jsonData.hints[i].food.label,
            calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL)
          });
        }
      });
    console.log(foods);
    foods = removeDuplicates(foods);

    for (let i = 0; i < foods.length; i++) {
      if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
        result.push(
          <FoodItem
            name={foods[i].name}
            calories={foods[i].calories}
            updateFoods={this.displayEatenFoods}
            isEaten={false}
            checkItem={this.checkItem}
          />
        );
      }
    }
    console.log(result);
    this.setState({ foodResult: result });
  }

  displayEatenFoods(name) {
    let tempEaten = [];
    let disableFlag = false;
    for (let i = 0; i < foods.length; i++) {
      if (foods[i].name.toUpperCase() == name.toUpperCase()) {
        if (this.checkItem(foods[i].calories, foods[i].name)) {
          tempEaten.push(
            <FoodItem
              name={foods[i].name}
              calories={foods[i].calories}
              updateFoods={this.displayEatenFoods}
              isEaten={true}
              checkItem={this.checkItem}
            />
          );
        } else {
          disableFlag = true;
        }
      }
    }
    tempEaten = removeDuplicates(tempEaten);
    tempEaten = this.state.EatensFoods.concat(tempEaten);
    this.setState({ EatensFoods: tempEaten });
    return disableFlag;
  }
  checkItem(cal, name) {
    let newRemainder = this.state.remaining - cal;
    if (newRemainder < 0) {
      this.setState({ allowance: "You can't eat " + name });
      return false;
    }
    this.setState({
      remaining: newRemainder,
      allowance: "You can eat " + name
    });
    return true;
  }

  render() {
    if (!this.state.goalEntered) {
      return (
        <center>
          <form onSubmit={this.setGoal}>
            <label>
              Please Enter your desired calories
              <input
                type="text"
                value={this.state.goal}
                onChange={this.changeGoal}
              />
            </label>
            <input type="submit" value="OK" />
          </form>
        </center>
      );
    }
    return (
      <div>
        <center>
          <h1>Maximum Calories:{this.state.goal}</h1>
          <h2>Remaining Calories:{this.state.remaining}</h2>
          <h3>{this.state.allowance}</h3>
          <form>
            <label>
              Search foods
              <input
                type="text"
                placeholder="Enter Name"
                value={this.state.foodSearch}
                ref={a => {
                  this.searchValue = a;
                }}
                onChange={this.changeFoodSearch}
              />
              <input
                type="text"
                placeholder="Calories,Min+,Max,Min-Max"
                value={this.state.calories}
                onChange={this.changeCalorieSearch}
              />
            </label>
          </form>
          {this.state.foodResult}
          <h2>Eaten Foods:</h2>
          {this.state.EatensFoods}
        </center>
      </div>
    );
  }
}

ReactDOM.render(<Main />, document.getElementById("root"));
Rick
  • 3,918
  • 9
  • 23
  • 34
Youssef
  • 90
  • 1
  • 9
  • could you try type `orangee`, notice the second e, would it search for orange for you? – leogoesger Jul 24 '18 at 22:10
  • Ok,I searched orangee and still gave me nothing but after deleting the second e, it displays the correct result for orange.At first,I thought there might be some kind of delay between the API response and the search bar changing,but typing {orange} and waiting for sometime still does nothing. – Youssef Jul 24 '18 at 22:23

1 Answers1

0

First of all I can't examine all your code if there should be some best practices instead of your logic but your problem is your updateResult function doing an async job but you are not waiting it to finish. This is your main problem. Deleting one word or deleting anything does not trigger the problem. Just type "o" then wait a little bit, then write anything and see the same problem occurs. Make your updateResult function async and put an await before your fetch.

async updateResult(name) {
    console.log(name);
    if (name == "") {
      this.setState({ foodResult: [] })
      return;
    }
    let result = [];
    let url = 'https://api.edamam.com/api/food-database/parser?app_id=e056fc58&app_key=somekeyhere&ingr=' + name;
    await fetch(url)
      .then(
        function (response) {
          return response.json();
        }
      ).then(function (jsonData) {
        for (let i = 0; i < jsonData.hints.length; i++) {
          foods.push({ name: jsonData.hints[i].food.label, calories: Math.round(jsonData.hints[i].food.nutrients.ENERC_KCAL) })
        }
      })
    console.log(foods);
    foods = removeDuplicates(foods);

    for (let i = 0; i < foods.length; i++) {
      if (foods[i].name.toUpperCase().includes(name.toUpperCase())) {
        result.push(
          <FoodItem name={foods[i].name} calories={foods[i].calories} updateFoods={this.displayEatenFoods} isEaten={false} checkItem={this.checkItem} />)
      }


    }
    console.log(result);
    this.setState({ foodResult: result });
  }

Instead of making your function an async one, you can continue the code with another .then method or methods. Just do not forget to return what you need for those .then methods from the previous ones.

devserkan
  • 15,490
  • 4
  • 29
  • 45
  • `await fetch(url).then(` the whole point of using async/await is to not needing `.then` anymore. I dont think that is the problem. `async/await` is just a wrapper around `promise` which is the `.then` syntax – leogoesger Jul 24 '18 at 22:36
  • I've updated my answer but not the code. `async` function is optional. Here OP does not wait all the result then immediately jump other parts like removing duplicates then rendering the result outside of the last `.then` method. – devserkan Jul 24 '18 at 22:40