React - “Is not a function” error when passing fun

2019-08-30 05:21发布

I'm relatively new to React and I'm working on a ToDo list style Recipe app. I asked a previous question here about refactoring a functional component so that recipe ingredients would appear on separate lines instead of all appearing as one single line paragraph.

I implemented the suggested changes and used a second .map function to iterate through ingredients and successfully got them to appear on separate lines. However, after implementing the changes now I get an error when the user clicks the edit button and attempts to edit the recipe ingredients.

The function component (Item.js) that handles displaying the recipe names and ingredients looks like this:

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


const Item = (props) => (
  <div>
    <div className="Recipe-Item-Container" key={props.text}>
      {props.items.map((item, index) => {
        return (
        <div className="Recipe-Item" key={index}>
          <h3>{item}</h3>

        // This p and the map function within it were the changes I made
        <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 when I click the edit button and attempt to edit the ingredients list I get this error:

Error showing when user attempts to edit ingredients

Here is my App.js component that stores state and is passing data to Item.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';

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

  onEditSubmit = (event) => {
    event.preventDefault();
    const {items, ingredients, inputValEdit, ingredientValEdit, editingIndex} = this.state;

    // Selects proper recipe item to edit
    items[editingIndex] = inputValEdit;
    ingredients[editingIndex] = ingredientValEdit;

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

}

Why am I getting this error in Item.js when the user attempts to edit the ingredients list? I'm thinking it involves the onEditSubmit function but I'm not sure.

1条回答
劳资没心,怎么记你
2楼-- · 2019-08-30 05:46

So, I saw that in your onEditSubmit you're doing this:

ingredients[editingIndex] = ingredientValEdit; // line 57

Your ingredients in the state is actually an array of arrays (Multi-dimensional). It's like this:

ingredients:[
        ["Pumpkin Puree ", "Sweetened Condensed Milk ", "Eggs ", "Pumpkin Pie Spice ", "Pie Crust "],
        ["Noodles ", "Tomato Sauce ", "(Optional) Meatballs "],
        ["Onion ", "Pie Crust "]
      ],

But after that line of code your array becomes something like this: I've entered 'Chicken Lollipop' as the Ingredient for eg.

ingredients:[
        "Chicken Lollipop",
        ["Noodles ", "Tomato Sauce ", "(Optional) Meatballs "],
        ["Onion ", "Pie Crust "]
      ],

So, the value at the index that was selected becomes a string instead of the array. In this case value at index 0 is now a string.

Thus, your render function in Item.js breaks since it's trying to map over the string which it cannot.

To fix this you can change line 57 to this:

ingredients[editingIndex] = [ingredientValEdit];

Now, each entry will be properly stored as an array and your code should work. The updated array would look like this:

ingredients:[
        ["Chicken Lollipop"],
        ["Noodles ", "Tomato Sauce ", "(Optional) Meatballs "],
        ["Onion ", "Pie Crust "]
      ]

Additional content (Not required for this answer):

However, since you always need the ingredients to be an array you can also split the input values using a delimiter for eg:

The user entered the ingredients as: 'Lollipop, Candy, Something'

In your line 57 you can also do this:

ingredients[editingIndex] = ingredientValEdit.split(',');
查看更多
登录 后发表回答