Nick Kinlen
Nick Kinlen

Reputation: 1406

React - Update state in class component based on text inputted by user after button click

I'm relatively new to React and creating a todo list style Recipe app. The user should be able to add a new recipe, delete an existing recipe, or edit an existing recipe. I am having trouble writing a function that allows the user to click the edit button and then submit a new recipe title and/or new ingredients to that recipe item.

Here is the project. How can I make it so after clicking one of the edit buttons and submitting the form the recipe title/ingredients of that recipe item are updated to show whatever the user submitted? I have a function called onSubmit in my App.js file that handles new recipe items being created by the user and adds them to the list. I am now trying to implement a similar function called onEditSubmit that allows the user to update existing recipe items but I am stuck.

Here is my App.js where the state lives:

 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
    };

  }

  // 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
    });
  }

  // Should edit and update a recipe item when user clicks edit button and then submits RecipeEditForm, not working
  onEditSubmit = (event) => {
    event.preventDefault()
    // This setState shoul erase previous recipe state and rewrite it with the details the user entered after editing that particular recipe item
    this.setState({
      items: this.state.inputValEdit,
      ingredients: this.state.ingredientValEdit,
      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) => {
    console.log('Edit button clicked');
    console.log('index is ' + index);
    this.setState({ showRecipeEditForm: !this.state.showRecipeEditForm });
  }

  // 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="Recipe-List">
        <h1>Recipe List</h1>

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

        <button onClick={this.AddRecipe}>Add New Recipe</button>

        {/* Shows form to edit recipe */}
        { this.state.showRecipeEditForm ?

          <RecipeEditForm
            inputValEdit={this.state.inputValEdit}
            handleChange={this.handleChange}
            ingredientValEdit={this.state.ingredientValEdit}
            onEditSubmit={this.onEditSubmit}
            closeRecipeForm={this.closeRecipeForm}
          />

          :null
        }

        {/* Shows form to add new recipe to the list */}
        { this.state.showRecipeForm ?

          <RecipeForm
            inputVal={this.state.inputVal}
            handleChange={this.handleChange}
            ingredientVal={this.state.ingredientVal}
            onSubmit={this.onSubmit}
            closeRecipeForm={this.closeRecipeForm}
          />

          :null
        }

      </div>
    );
  }

}


And here is my Item.js function container for the data. This container works fine when onSubmit utilizes it to add a new recipe. But when onEditSubmit attempts to use it I get an error, props.items.map is not a function.

import React from 'react';

const Item = (props) => (
  <div>
    <div className="Recipe-Item" key={props.text}>

        {props.items.map((item, index) =>
        <div className="Recipe-Item-Container">
        <ul>
          <li key={index}>
            {item}
          </li>
          <li>
            {props.ingredients[index]}
          </li>
        </ul>

          <button onClick={() => props.edit(item, index)}>Edit</button>
          <button onClick={() => props.delete(item, index)}>Delete</button>

        </div>
      )}
    </div>
  </div>
)


export default Item;

Pretty sure I'm making a noob React error but I'm not sure what the problem is.

Edited to correct syntax error in onSubmitError function: inputVal changed to inputValEdit, ingredientVal changed to ingredientValEdit

Here are the functional components for onSubmit:

import React, { Component } from 'react';


const RecipeForm = (props) => {
  return (
    <div>
      <form className="Recipe-Form" onSubmit={props.onSubmit}>
        <p className="x-close" onClick={props.closeRecipeForm}>X</p>
        <div className="form-content">
          <label>Recipe Name</label>

          <input
            name="inputVal"
            value={props.inputVal}
            onChange={props.handleChange}
          />

          <label>Ingredients</label>
          <input
            name="ingredientVal"
            value={props.ingredientVal}
            onChange={props.handleChange}
          />

          <button>Submit</button>
        </div>
      </form>
    </div>
  );
}

export default RecipeForm;

And here is the functional component for onEditSubmit:

import React, { Component } from 'react';


const RecipeEditForm = (props) => {
  return (
    <div>
      <form className="Recipe-Form Edit-Form" onSubmit={props.onEditSubmit}>
        <p className="x-close" onClick={props.closeRecipeForm}>X</p>
        <div className="form-content">
          <h1>This is the edit form</h1>

          <label>Edit Recipe</label>
          <input
            name="inputValEdit"
            value={props.inputValEdit}
            onChange={props.handleChange}
          />

          <label>Ingredients</label>
          <input
            name="ingredientValEdit"
            value={props.ingredientValEdit}
            onChange={props.handleChange}
          />

          <button>Submit</button>
        </div>
      </form>
    </div>
  );
}

export default RecipeEditForm;

Upvotes: 1

Views: 359

Answers (2)

Dacre Denny
Dacre Denny

Reputation: 30360

You need to extend your <App/> state so that your app can track which recipe item in your list of recipes is currently being edited.

You could for instance, store the index of the recipe data that your user is editing. You would then use this index to update the correct recipe in your list during the onEditSubmit() function:

 onEditSubmit = (event) => {
    event.preventDefault()

    // Get the index of the recipe we're currently editing
    const currentlyEditingIndex = this.state.currentlyEditingIndex

    // Clone data arrays for mutation
    const items = [].concat(this.state.items)
    const ingredients = [].concat(this.state.ingredients)

    // Update data arrays
    items[ currentlyEditingIndex ] = this.state.inputValEdit;
    ingredients[ currentlyEditingIndex ] = this.state.ingredientValEdit;

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

For this to work, you would also need to update your edit function like so:

edit = (item, index) => {
    console.log('Edit button clicked');
    console.log('index is ' + index);
    this.setState({ 

       // Store the index of the recipe being edited
       currentlyEditingIndex : index, 
       showRecipeEditForm: !this.state.showRecipeEditForm 
    });
  }

Hope this helps!

Upvotes: 0

izb
izb

Reputation: 582

onEditSubmit = (event) => {
  event.preventDefault()
  // This setState shoul erase previous recipe state and rewrite it with the 
  details the user entered after editing that particular recipe item
  this.setState({
    items: this.state.inputVal,
    ingredients: this.state.ingredientVal,
    showRecipeEditForm: false
  });
}

you're setting the items state field to a string. You should change it to something like:

onEditSubmit = (e) => {
  e.preventDefault();
  const {items, ingredients, inputVal, ingredientVal, editingIndex} = this.state;
  items[editingIndex] = inputVal;
  ingredients[editingIndex] = ingredientVal;
  this.setState({
    items,
    ingredients,
    inputVal: '',
    ingredientVal: ''
  });
}

Where editingIndex you can store in your edit function: setState({editingIndex: index}

Upvotes: 2

Related Questions