React - JSX syntax issue, and how to iterate with

2019-03-04 16:54发布

I'm a React noob and making a ToDo list style Recipe List app. I have a functional component, Item.js, and I am using JSX and the map function to iterate through each recipe item and display them. I want each recipe item to appear on a new line, but when I use .map to iterate through them React puts the entire list of recipe items into one p tag instead of one p tag for each item.

How can I iterate through the recipe items and display them on separate lines? Even if I try to display them as an unordered list React wants to throw each item in one li tag.

Here is my code:

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) => 
        <div className="Recipe-Item" key={index}>

            <h3>{item}</h3>

            <p>{props.ingredients[index]}</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;

Also on the {props.items.map((item, index) => line if I add a curly brace after the => I get an error. I have a React/JSX linter installed and it isn't catching anything. What's the issue?

I know this is probably a noob error but JSX is throwing me for a loop here.

2条回答
Lonely孤独者°
2楼-- · 2019-03-04 17:18

If you add curly braces after the fat arrow, you will have to explicitly return the JSX.

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>

              <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>
)
查看更多
别忘想泡老子
3楼-- · 2019-03-04 17:28

Here is the working version.

class App extends React.Component {
  state = {
    items: [ "Pumpkin Pie", "Spaghetti", "Onion Pie" ],
    ingredients: [
      [ "Pumpkin Puree", "Sweetened Condensed Milk", "Eggs", "Pumpkin Pie Spice", "Pie Crust" ],
      [ "Noodles", "Tomatoe", "Sauce", "Meatballs" ],
      [ "Onion", "Pie Crust" ],
    ],
  }

  render() {
    return (
      <div className="box">
        <Item items={this.state.items} ingredients={this.state.ingredients} />
      </div>
    );
  }
}

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

      {props.items.map( ( item, index ) => (
        <div className="Recipe-Item" key={item}>

          <h3>{item}</h3>
          <ul>
            {
              props.ingredients[ index ].map( ingredient =>
                <li key={ingredient}>{ingredient}</li> )
            }
          </ul>


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

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

But if I were you I would change my state shape. Something like that:

class App extends React.Component {
  state = {
    items: [
      {
        name: "Pumpkin Pie",
        ingredients: [
          "Pumpkin Puree",
          "Sweetened Condensed Milk",
          "Eggs",
          "Pumpkin Pie Spice",
          "Pie Crust"
        ]
      },
      {
        name: "Spaghetti",
        ingredients: ["Noodles", "Tomatoe", "Sauce", "Meatballs"]
      },
      {
        name: "Onion Pie",
        ingredients: ["Onion", "Pie Crust"]
      }
    ]
  };

  removeItem = item => {
    const newItems = this.state.items.filter(el => el.name !== item.name);
    this.setState({ items: newItems });
  };

  editItem = item => alert(`${item.name} will be edited`);

  renderItems = () =>
    this.state.items.map(item => (
      <Item
        key={item.name}
        item={item}
        removeItem={this.removeItem}
        editItem={this.editItem}
      />
    ));

  render() {
    return <div className="box">{this.renderItems()}</div>;
  }
}

const Item = props => {
  const { item, removeItem, editItem } = props;
  const handleRemove = () => removeItem(item);
  const handleEdit = () => editItem(item);

  return (
    <div>
      <div className="Recipe-Item-Container" key={props.text}>
        <div className="Recipe-Item">
          <h3>{item.name}</h3>
          <ul>
            {item.ingredients.map(ingredient => (
              <li key={ingredient}>{ingredient}</li>
            ))}
          </ul>
          <div className="buttons-container">
            <button className="edit-button" onClick={handleEdit}>
              Edit
            </button>
            <button className="delete-button" onClick={handleRemove}>
              Delete
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Changes

  • State shape: Instead of holding two arrays, we are keeping an object per item. This object has a name and ingredients property. Maybe in the future, it may have a unique id? Objects are flexible.
  • Instead of passing all the items to an Item component, we are mapping the items in the parent component and pass just one item to the Item component.
  • We still have handler functions defined in the parent. But, we are not using them directly in the button's callback with an arrow function. So, they are not recreated in every render. Also, we don't have to use an index to pass the items back to the parent. We have the item prop itself! You can see how we handle the remove functionality: with .filter You can apply the same functionality to other functions. .map, .filter, Object.assign or spread syntax are all good tools. Just, avoid mutating your state directly.
查看更多
登录 后发表回答