0
votes

I'm relatively new to React and creating a ToDoList style Recipe App. The user should be able to add new recipes to the list as well as delete or edit existing recipes.

I ran into an issue getting the ingredients to display on separate lines and asked this question on how to do that. The end result was that I added a second .map function within my container to iterate through ingredients and display each one as a new paragraph element. Here is my container, Item.js:

import React from 'react';
import Button from 'react-bootstrap/lib/Button';


const Item = (props) => (
  <div>
    <div className="Recipe-Item-Container" key={props.text}>
      {/* Iterates through recipe item names and displays them */}
      {props.items.map((item, index) => {
        return (
        <div className="Recipe-Item" key={index}>
          <h3>{item}</h3>

         // This is what I added
        {/* Iterates through recipe ingredients and displays them*/}
        <p className="ingredients-list">
          {props.ingredients[index].map((ingredient, ingredientIndex) => {
            return (
              <div className="ingredient" key={ingredient}>
                {ingredient}
              </div>
            )
          })}
        </p>

        <div className="buttons-container">
          <Button className="edit-button" onClick={() => props.edit(item, index)}>Edit</Button>
          <Button className="delete-button" onClick={() => props.delete(item, index)}>Delete</Button>
          </div>
        </div>
      );
    }
  )}
  </div>
</div>
)

export default Item;

Now the initial recipes within my state properly display ingredients on new lines, and any recipe item the user edits also displays ingredients on a new line. Exactly what I wanted.

The issue is when the user adds a new recipe the ingredients aren't displayed on new lines the way I want. If you click the add new recipe button and add a new one the ingredients are displayed side by side and as one single paragraph element.

I have two functions that handle adding a new recipe and editing an existing recipe, onSubmit and onEditSubmit. onEditSubmit is working fine because when recipes are edited the ingredients display properly on separate lines. onSubmit is the issue. How can I change onSubmit and get it to display ingredients on new lines and as separate paragraphs? I think this is an issue with how I am setting state within onSubmit and might involve the use of the spread operator.

Here is my App.js:

import React, { Component } from 'react';
import Item from './Item';
import './App.css';
import ModalComponent from './Modal.js';
import Button from 'react-bootstrap/lib/Button';
import EditModalComponent from './EditModal.js';
import SimpleStorage from "react-simple-storage";


export default class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      items: ["Pumpkin Pie", "Spaghetti", "Onion Pie"],
      ingredients:[
        ["Pumpkin Puree ", "Sweetened Condensed Milk ", "Eggs ", "Pumpkin Pie Spice ", "Pie Crust "],
        ["Noodles ", "Tomato Sauce ", "(Optional) Meatballs "],
        ["Onion ", "Pie Crust "]
      ],

      // Recipe name and ingredients
      inputVal: '',
      ingredientVal: '',
      // Recipe name and ingredients when user is editing existing recipe
      inputValEdit: '',
      ingredientValEdit: '',
      // Controls whether forms are displayed or hidden
      showRecipeForm: false,
      showRecipeEditForm: false,
      // Index to select which recipe item is being edited
      editingIndex: ''
    };

  }

  // Get text user inputs for recipes
  handleChange = (event) => {
    this.setState({ [event.target.name]: event.target.value });
  };


  // When user submits recipe this adds it to the list
  onSubmit = (event) => {
    event.preventDefault();

    this.setState({
      items: [...this.state.items, this.state.inputVal],
      ingredients: [...this.state.ingredients, [this.state.ingredientVal]],
      showRecipeForm: false
    });

  }

  // When user edits existing recipe this adds it to the list
  onEditSubmit = (event) => {
    event.preventDefault();
    const {items, ingredients, inputValEdit, ingredientValEdit, editingIndex} = this.state;

    // Selects proper recipe item to edit
    items[editingIndex] = inputValEdit;
    ingredients[editingIndex] = ingredientValEdit.split(',');


    this.setState({
      items: items,
      ingredients: ingredients,
      inputVal: '',
      ingredientVal: '',
      showRecipeEditForm: false
    });
  }

  closeRecipeForm = () => {
    this.setState({
      showRecipeForm: false,
      showRecipeEditForm: false
    });
  }

  // Shows recipe
  AddRecipe = (bool) => {
    this.setState({
      showRecipeForm: bool
    });
  }

  // Is called when one of the edit recipe buttons is clicked, shows RecipeEditForm
  edit = (item, index) => {
    this.setState({
      showRecipeEditForm: !this.state.showRecipeEditForm,
      editingIndex: index
    });
  }

  // Deletes recipe item from the list
  delete = (item, index) => {
     this.setState({
      ingredients : this.state.ingredients.filter((_, i) => i !== index),
      items: this.state.items.filter((_, i) => i !== index)
    });
  }


  render() {
    return (
      <div className="container">

        {/* Handles storing data in local sessions via react-simple-storage*/}
        <SimpleStorage parent={this} />

        <h1>Recipe List</h1>


        <ModalComponent
          inputVal={this.state.inputVal}
          handleChange={this.handleChange}
          ingredientVal={this.state.ingredientVal}
          onSubmit={this.onSubmit}
          addRecipe={this.addRecipe}
          showRecipeForm={this.state.showRecipeForm}
          closeRecipeForm={this.closeRecipeForm}
        />

        <EditModalComponent
          inputValEdit={this.state.inputValEdit}
          handleChange={this.handleChange}
          ingredientValEdit={this.state.ingredientValEdit}
          onEditSubmit={this.onEditSubmit}
          closeRecipeForm={this.closeRecipeForm}
          addRecipe={this.addRecipe}
          showRecipeEditForm={this.state.showRecipeEditForm}
        />


        <Item
          items={this.state.items}
          ingredients={this.state.ingredients}
          edit={this.edit}
          delete={this.delete}
        />

      <Button className="add-recipe-button" onClick={this.AddRecipe}>Add New Recipe</Button>

      </div>
    );
  }

}

Do I need to make onSubmit more similar to onEditSubmit? If so how can I do this?

1
Is not clear what the this.ingredientVal variable is, if it is a string with the same format as ingredientValEdit , you'll need to use [...this.state.ingredients, this.state.ingredientVal.split(",")] instead of [...this.state.ingredients, [this.state.ingredientVal]] - Titus
I ran into this issue with strings/arrays in my state: stackoverflow.com/questions/52357428/… - Nick Kinlen
Yes but there you were using ingredients: [...this.state.ingredients, this.state.ingredientVal] not ingredients: [...this.state.ingredients, this.state.ingredientVal.split(",")]. - Titus

1 Answers

1
votes

In your store, your ingredients variable is an array of arrays.[ [a, b] , [c, d]]

You need to follow this format as you did in onEditSubmit. The .split() function return an array and save it into state.

Change the part in onSubmit

this.setState({
    items: [...this.state.items, this.state.inputVal],
    ingredients: [...this.state.ingredients, [this.state.ingredientVal]],
    showRecipeForm: false
});

to

this.setState({
    items: [...this.state.items, this.state.inputVal],
    ingredients: [...this.state.ingredients, this.state.ingredientVal.split(',')],
    showRecipeForm: false
});