Home > Software engineering >  Why does a list element not initially render but does after a state change in React?
Why does a list element not initially render but does after a state change in React?

Time:06-22

If necessary, the project's current code be viewed on Github

I've ran into an issue with my current React/TypeScript project, a cocktail recipe viewer, where the ingredients list will not show up initially when a drink is selected; however, after a second drink is selected the ingredients list shows up as intended.

Screenshot of the ingredients not showing up during an initial search

Screenshot of the ingredients showing up correctly after a second drink is selected

Technical details:

The cocktail data is fetched from TheCocktailDB's free API and assigned to a "data" state. When a specific drink is selected it is set to a "drink" state. The drink's photo, ingredients, recipe, and directions of how to make the drink are shown in a modal pop-up.

I utilized useEffect to run two functions each time a drink is selected to parse the ingredients and measurements for the chosen drink:

    useEffect(() => {
getIngredients(drink);
getMeasurements(drink); }, [drink]);

The getIngredients and getMeasurements functions are similar:

 const getIngredients = (drink: IDrinkProps) => {
/*Each drink object has the properties strIngredient1 through strIngredient15
this checks each property to determine if it's a string and checks that it's not an empty string, if it passes
those two conditions then it is pushed to the filteredIngredients array and displayed in the modal popup for the drink the user selects.*/

for (let i = 0; i <= 14; i  ) {
  //Checks that the current ingredient is a string, if not the message is console logged
  if (typeof drink[`strIngredient${i}`] !== "string") {
    console.log(typeof drink[`strIngredient${i}`], "Detected");

    //Checks if the current ingredient is an empty string if so the message is console logged
  } else if (drink[`strIngredient${i}`] === "") {
    console.log("Empty string detected");
  } else {
    //If the checks above pass the ingredient is pushed to the filteredIngredients array

    filteredIngredients.push(drink[`strIngredient${i}`]);
  }
  //Once the for loop finishes the filteredIngredients array is set to the ingredients state and displayed in the modal pop-up
  setIngredients(filteredIngredients);
}};

The getMeasurements function runs a third function, getExactRecipe upon it's conclusion:

   const getMeasurements = (drink: IDrinkProps) => {

for (let i = 0; i <= 14; i  ) {
  //Checks that the current measurement is a string, if not the message is console logged
  if (typeof drink[`strMeasure${i}`] !== "string") {
    console.log(typeof drink[`strMeasure${i}`], "Detected");

    //Checks if the current measurement is an empty string if so the message is console logged
  } else if (drink[`strMeasure${i}`] === "") {
    console.log("Empty string detected");
  } else {
    //If the checks above pass the measurement is pushed to the filteredMeasurements array

    filteredMeasurements.push(drink[`strMeasure${i}`]);
  }
  //Once the for loop finishes the filteredMeasurements array is set to the measurements state and displayed in the modal pop-up
}
setMeasurements(filteredMeasurements);
getExactRecipe(ingredients, measurements); };

The getExactRecipe function combines the ingredients with their respective measurements for display in the modal pop-up:

 const getExactRecipe = (
    ingredients: (string | undefined)[],
    measurements: (string | undefined)[]
  ) => {
    let exactArray: string[] = [];
    if (ingredients.length > 0) {
      for (let i = 0; i < filteredIngredients.length; i  ) {
        if (
          `${filteredIngredients[i]}` === "undefined" ||
          `${measurements[i]}` === "undefined"
        ) {
          //Do nothing
        } else {
          exactArray.push(`${filteredIngredients[i]} ${measurements[i]}`);
          setExactRecipe(exactArray);
        }
      }
    }
  };

I am uncertain if this issue has to do with the useEffect statement or getExactRecipe function possibly. Or if it is caused by another unknown culprit. Any help is much appreciated as this has been driving me crazy!

CodePudding user response:

I didn't check your entire code, as you should really try to condense your problem into a smaller example, but I'm guessing your issues is this...

useEffect(() => {
  getIngredients(drink);
  getMeasurements(drink); 
}, [drink]);

...

const getIngredients = (drink: IDrinkProps) => {
  ...
  setIngredients(filteredIngredients);
};

...

const getMeasurements = (drink: IDrinkProps) => {
  ...
  getExactRecipe(ingredients, measurements);
};

You're using ingredients in the same useEffect block as setIngredients was called. I'm guessing you were hoping to use the newly set value, but that's not how hooks work. This means that your getMeasurements function is still using the previous ingredients value.

Note also that you're making this same mistake with setMeasurements and measurements.

An alternative to this, would be to split the useEffect in to 2 separate blocks.

useEffect(() => {
  ... // Ingredients
}, [drink]);

useEffect(() => {
  ... // Measurements
}, [drink, ingredients]); // Note: that this second useEffect also depends on ingredients!
  • Related