react本地储存_如何使用React和本地存储构建freeCodeCamp的配方框

react本地储存

by Edward Njoroge

爱德华·尼约格(Edward Njoroge)

如何使用React和本地存储构建freeCodeCamp的配方框 (How to build freeCodeCamp’s recipe box using React and local storage)

I completed my first edition of the Free Code Camp recipe box project on May 3, 2018. I put it up here for review. Then I didn’t check the reviews for a few weeks. When I returned, I was shocked to learn that I had overlooked an important feature in forms.

我于2018年5月3日完成了第一版的Free Code Camp食谱盒项目。我将其放在此处进行审核。 然后,我有几个星期没有检查评论了。 当我回来时,得知自己忽略了表格中的一个重要功能,我感到震惊。

I know. Terrible mistake. My form allowed for the creation of an empty recipe. This oversight shows the importance of allowing other people to review your code.

我知道。 可怕的错误。 我的表格允许创建一个空配方。 这种疏忽表明允许其他人查看您的代码的重要性。

It turned out I wasn’t the only one that missed this important feature. I checked freeCodeCamp’s example project for the recipe box (here) and it was missing the same feature. Validation is not mentioned in the user stories (here) either.

事实证明,我并不是唯一一个错过这一重要功能的人。 我检查了freeCodeCamp的示例项目中的配方框( 在此处 ),它缺少相同的功能。 用户故事( 此处 )中也未提及验证。

I figured that if I included validation in my project, I could try to convince freeCodeCamp to make my recipe box the example project for this challenge. So I restarted the project, and during this process I was inspired to write this Medium post.

我认为,如果我在项目中包含验证,则可以尝试说服freeCodeCamp使我的配方盒成为应对这一挑战的示例项目。 因此,我重新启动了该项目,在此过程中,我受到启发而撰写了这篇Medium帖子。

建立配方盒 (Building the recipe box)

For this project, we will use create-react-app, React bootstrap, and bootstrap CSS.

对于此项目,我们将使用create-react-app,React引导程序和引导CSS。

步骤1:设置React环境并添加React引导程序。 (Step 1: Set up the React environment and add React bootstrap.)
npx create-react-app recipe-box
npm install react-bootstrap --save

We will create a file directory that resembles the one below:

我们将创建一个类似于以下内容的文件目录:

We delete favicon.ico and manifest.json from the public folder, and everything except index.js and index.css from the src folder. Inside the SRC folder, create a components folder and a CSS folder. Move index.css to the CSS folder.

我们从公用文件夹中删除favicon.ico和manifest.json,并从src文件夹中删除除index.js和index.css之外的所有内容。 在SRC文件夹中,创建一个components文件夹和一个CSS文件夹。 将index.css移到CSS文件夹。

步骤2:在index.html中设置html。 (Step 2: Set up the html in index.html.)

In index.html:

在index.html中:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Recipe Box</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="This is a Free Code Camp Project called Recipe Box">
    <meta name="keywords" content="HTML, CSS, JAVASCRIPT, REACTJS">
    <meta name="author" content="Your Name">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link href="https://fonts.googleapis.com/css?family=Mina" rel="stylesheet">
  </head>
  <body>
    <!--set up a div where all the code will be rendered-->
    <div class="container" id="app"></div>
  </body>
</html>
步骤3:设置配方框的第一个视图。 (Step 3: Set up the first view of the recipe box.)

In index.js, we create an initial list of recipes in this.state and display them.

在index.js中,我们在this.state中创建配方的初始列表并显示它们。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import './css/index.css';
//create the main class for displaying the recipes
class Recipe extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      recipes: [
        {name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},
        {name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},
        {name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}
      ]
    };
  }
  render() {
    const recipes = this.state.recipes;
    return(
      <div className="jumbotron">
        <h1>RECIPE BOX</h1>
        <PanelGroup accordion id="recipes">
          {recipes.map((recipe, index) => (
            <Panel eventKey={index} key={index}>
              <Panel.Heading>
                <Panel.Title className="title" toggle>{recipe.name}</Panel.Title>
              </Panel.Heading>
              <Panel.Body collapsible>
                <ListGroup>
                  {recipe.ingredients.map((ingredient, index) => (
                    <ListGroupItem key={index}>{ingredient}</ListGroupItem>
                  ))}
                </ListGroup>
                <ButtonToolbar>
                  <Button bsStyle="warning">Edit</Button>
                  <Button bsStyle="danger">Delete</Button>
                </ButtonToolbar>
              </Panel.Body>
            </Panel>
          ))}
        </PanelGroup>
        <Button bsStyle="primary">Add Recipe</Button>
      </div>
    );
  }
};

ReactDOM.render(<Recipe />, document.getElementById('app'));

In index.css:

在index.css中:

h1, li, .title {
  font-family: 'Mina';
}
h1, li {
  text-align: center;
}
.title {
  background-color: #D8BFD8;
  font-size: 20px;
}
li {
  list-style-type: none;
  font-size: 18px;
}

Result:

结果:

步骤4:创建添加配方功能。 (Step 4: Creating the Add Recipe function.)

We are now ready to add recipes. We create a file called addrecipe.js inside the components folder.

我们现在准备添加配方。 我们在components文件夹中创建一个名为addrecipe.js的文件。

Recipes will be added through a modal form. We must first be able to activate and deactivate the modal. We create a state called showAdd and set it to false. Then we create a function called showAddModal() that changes showAdd to true if its currently false and vice versa.

食谱将以模态形式添加。 我们必须首先能够激活和停用模式。 我们创建一个名为showAdd的状态并将其设置为false。 然后,我们创建一个名为showAddModal()的函数,如果showAdd当前为false,则将showAdd更改为true,反之亦然。

When the “Add Recipe” button is clicked, showAdd will turn to true and the modal will be displayed. Therefore, showAdd and showAddModal() must be passed as props to addrecipe.js.

单击“添加配方”按钮时,showAdd将变为true,并显示模态。 因此,必须将showAdd和showAddModal()作为props传递给addrecipe.js。

To add a recipe, an addRecipe() function that takes the argument ‘recipe’ will be created. It takes the details for the new recipe, and pushes them to the end of the recipe state array. This function will also be passed as a prop to addrecipe.js.

要添加配方,将创建一个带有参数“ recipe”的addRecipe()函数。 它获取新配方的详细信息,并将其推送到配方状态数组的末尾。 此函数还将作为propre传递给addrecipe.js。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import {AddRecipe} from './components/addrecipe';
import './css/index.css';
//create the main class for displaying the recipes
class Recipe extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      recipes: [
        {name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},
        {name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},
        {name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}
      ],
      showAdd: false
    };
    this.showAddModal = this.showAddModal.bind(this);
    this.addRecipe = this.addRecipe.bind(this);
  }
  showAddModal() {//show the new recipe modal
    this.setState({showAdd: !this.state.showAdd});
  }
  addRecipe(recipe) {//create a new recipe
    let recipes = this.state.recipes;
    recipes.push(recipe);
    this.setState({recipes: recipes});
    this.showAddModal();
  }
  render() {
    const recipes = this.state.recipes;
    return(
      <div className="jumbotron">
        <h1>RECIPE BOX</h1>
        <PanelGroup accordion id="recipes">
          {recipes.map((recipe, index) => (
            <Panel eventKey={index} key={index}>
              <Panel.Heading>
                <Panel.Title className="title" toggle>{recipe.name}</Panel.Title>
              </Panel.Heading>
              <Panel.Body collapsible>
                <ListGroup>
                  {recipe.ingredients.map((ingredient, index) => (
                    <ListGroupItem key={index}>{ingredient}</ListGroupItem>
                  ))}
                </ListGroup>
                <ButtonToolbar>
                  <Button bsStyle="warning">Edit</Button>
                  <Button bsStyle="danger">Delete</Button>
                </ButtonToolbar>
              </Panel.Body>
            </Panel>
          ))}
        </PanelGroup>
        <Button bsStyle="primary" onClick={this.showAddModal}>Add Recipe</Button>
        <AddRecipe onShow={this.state.showAdd} onAdd={this.addRecipe} onAddModal={this.showAddModal} />
      </div>
    );
  }
};

ReactDOM.render(<Recipe />, document.getElementById('app'));
ReactDOM.render(<Recipe />, document.getElementById('app'));

In addrecipe.js, we create a state that holds the new recipe name and recipe ingredients, and the initial values are empty strings. We will then change the state every time we change the contents of the form as we would in a markdown. This will make form validation easier.

在addrecipe.js中,我们创建一个状态,其中包含新的配方名称和配方成分,并且初始值为空字符串。 然后,我们每次更改表单内容时都将更改状态,就像在markdown中一样。 这将使表单验证更加容易。

Instead of displaying form errors for validation, we use regular expression to ensure that we only save a recipe if some conditions are met. These conditions are:

我们使用正则表达式来确保仅在满足某些条件的情况下保存配方,而不是显示用于验证的表单错误。 这些条件是:

(a) Both the recipe name and ingredients sections must not be empty, that is both must have at least one character.

(a)食谱名称和配料部分都不能为空,即都必须至少包含一个字符。

(b) The form recipe name cannot begin with a space. This ensures that the recipe name begins with at least one alphanumeric character or symbol.

(b)表单配方名称不能以空格开头。 这样可以确保配方名称至少以一个字母数字字符或符号开头。

(c)The form recipe ingredients cannot begin or end with a space or comma. This is because ingredients will be split by commas into an array that is then displayed as a list like our current ingredients are.

(c)配方成分不能以空格或逗号开头或结尾。 这是因为成分将通过逗号分隔成一个数组,然后像我们当前的成分一样显示为列表。

The modal will have a Save Recipe button which will be disabled until all conditions are met. When save recipe is clicked, the recipe will be added to our recipe box.

该模态将具有一个“保存配方”按钮,该按钮将被禁用,直到满足所有条件为止。 单击保存配方后,该配方将添加到我们的配方框中。

In addrecipe.js:

在addrecipe.js中:

//import the necessary files
import React from 'react';
import {Modal,ControlLabel,FormGroup,FormControl,Button} from 'react-bootstrap';

//create a class for displaying the modal for adding a new recipe and export it
export class AddRecipe extends React.Component {
  constructor(props) {//create a state to handle the new recipe
    super(props);
    this.state = {name: "", ingredients: ""};
    this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this);
    this.handleRecipeIngredientsChange = this.handleRecipeIngredientsChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleCancel = this.handleCancel.bind(this);
  }
  handleRecipeNameChange(e) {//change the name to reflect user input
    this.setState({name: e.target.value});
  }
  handleRecipeIngredientsChange(e) {//change the ingredients to reflect user input
    this.setState({ingredients: e.target.value});
  }
  handleSubmit(e) {//get the recipe data, manipulate it and call the function for creating a new recipe
    e.preventDefault();
    const onAdd = this.props.onAdd;
    const regExp = /\s*,\s*/;
    var newName = this.state.name;
    var newIngredients = this.state.ingredients.split(regExp);
    var newRecipe = {name: newName, ingredients: newIngredients};
    onAdd(newRecipe);
    this.setState({name: "", ingredients: ""});
  }
  handleCancel() {
    const onAddModal = this.props.onAddModal;
    this.setState({name: "", ingredients: ""});
    onAddModal();
  }
  render() {
    const onShow = this.props.onShow;
    var regex1 = /^\S/;
    var regex2 = /^[^,\s]/;
   var regex3 = /[^,\s]$/;
    const validRecipe = regex1.test(this.state.name) && regex2.test(this.state.ingredients) && regex3.test(this.state.ingredients);
    return(
      <Modal show={onShow} onHide={this.handleCancel}>
        <Modal.Header closeButton>
          <Modal.Title>New Recipe</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <FormGroup controlId="formControlsName">
            <ControlLabel>Recipe Name</ControlLabel>
            <FormControl type="text" required onChange={this.handleRecipeNameChange} value={this.state.name} placeholder="Enter Name" />
          </FormGroup>
          <FormGroup controlId="formControlsIngredients">
            <ControlLabel>Recipe Ingredients</ControlLabel>
            <FormControl componentClass="textarea" type="text" required onChange={this.handleRecipeIngredientsChange} value={this.state.ingredients} placeholder="Enter Ingredients(separate by commas)" />
          </FormGroup>
        </Modal.Body>
        <Modal.Footer>
          <Button disabled={!validRecipe} bsStyle="success" onClick={this.handleSubmit}>Save Recipe</Button>
        </Modal.Footer>
      </Modal>
    );
  }
};

Result:

结果:

步骤5:创建“编辑配方”功能。 (Step 5: Creating the Edit Recipe function.)

We are now ready to edit recipes. We create a file called editrecipe.js inside the components folder.

现在我们可以编辑配方了。 我们在components文件夹内创建一个名为editrecipe.js的文件。

Recipes will be edited through a modal form. We must first be able to activate and deactivate the modal. We create a state called showEdit and set it to false. Then we create a function called showEditModal() that changes showEdit to true if its currently false and vice versa. When the “Edit” button is clicked, showEditModal() will run, showEdit will turn to true, and the modal will be displayed.

配方将通过模式表格进行编辑。 我们必须首先能够激活和停用模态。 我们创建一个名为showEdit的状态并将其设置为false。 然后,我们创建一个名为showEditModal()的函数,如果showEdit当前为false,则将showEdit更改为true,反之亦然。 单击“编辑”按钮后,将运行showEditModal(),showEdit将变为true,并显示模式。

We will also need a way to ensure that the correct recipe is displayed on the form fields for editing. We create a state called currentlyEditing and set it to 0. We then ensure that the details of this.state.recipes[currentlyEditing] are displayed on the form.

我们还需要一种方法来确保在表单字段上显示正确的配方以进行编辑。 我们创建一个名为currentlyEditing的状态并将其设置为0。然后确保this.state.recipes [currentlyEditing]的详细信息显示在表单上。

Since 0 is the default, whenever Edit Recipe is clicked, the form will only show the details of the first recipe. We need a way to update currentlyEditing to the index of the recipe we want displayed.

由于默认值为0,因此每当单击“编辑配方”时,表格将仅显示第一个配方的详细信息。 我们需要一种方法来将currentEditing更新为要显示的配方的索引。

In showEditModal(), we pass index as an argument and this argument will be equal to the index of the current recipe. Now when the “Edit Recipe” button is clicked, showEditModal() will run, showEdit will turn to true, currentlyEditing will become the index of the recipe, and the modal will be displayed with the correct recipe’s information. Therefore, showEdit and showEditModal(index) must be passed as props to editrecipe.js.

在showEditModal()中,我们将index作为参数传递,并且该参数将等于当前配方的索引。 现在,当单击“编辑配方”按钮时,showEditModal()将运行,showEdit将变为true,当前编辑将成为配方的索引,并且模态将显示正确的配方信息。 因此,必须将showEdit和showEditModal(index)作为props传递给editrecipe.js。

To edit a recipe, an editRecipe() function that takes the arguments newName, newIngredients, and currentlyEditing will be created. In this function, we use currentlyEditing (which is now the index of the recipe we are editing) to identify that recipe and set its name to the newName and its ingredients to the newIngredients. Therefore, editRecipe, the recipe we need to edit, and currentlyEditing must be passed as props to editrecipe.js.

要编辑配方,将创建一个带有参数newName,newIngredients和currentEditing的editRecipe()函数。 在此功能中,我们使用currentlyEditing(现在是我们正在编辑的配方的索引)来标识该配方,并将其名称设置为newName,并将其成分设置为newIngredients。 因此,必须将editRecipe,我们需要编辑的配方以及当前编辑作为道具传递给editrecipe.js。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import {AddRecipe} from './components/addrecipe';
import {EditRecipe} from './components/editrecipe';
import './css/index.css';
//create the main class for displaying the recipes
class Recipe extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      recipes: [
        {name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},
        {name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},
        {name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}
      ],
      showAdd: false,
      showEdit: false,
      currentlyEditing: 0
    };
    this.showAddModal = this.showAddModal.bind(this);
    this.showEditModal = this.showEditModal.bind(this);
    this.addRecipe = this.addRecipe.bind(this);
    this.editRecipe = this.editRecipe.bind(this);
  }
  showAddModal() {//show the new recipe modal
    this.setState({showAdd: !this.state.showAdd});
  }
  showEditModal(index) {//show the edit recipe modal
    this.setState({showEdit: !this.state.showEdit, currentlyEditing: index});
  }
  addRecipe(recipe) {//create a new recipe
    let recipes = this.state.recipes;
    recipes.push(recipe);
    this.setState({recipes: recipes});
    this.showAddModal();
  }
  editRecipe(newName, newIngredients, currentlyEditing) {//edit an existing recipe
    let recipes = this.state.recipes;
    recipes[currentlyEditing] = {name: newName, ingredients: newIngredients};
    this.setState({recipes: recipes});
    this.showEditModal(currentlyEditing);
  }
  render() {
    const recipes = this.state.recipes;
    return(
      <div className="jumbotron">
        <h1>RECIPE BOX</h1>
        <PanelGroup accordion id="recipes">
          {recipes.map((recipe, index) => (
            <Panel eventKey={index} key={index}>
              <Panel.Heading>
                <Panel.Title className="title" toggle>{recipe.name}</Panel.Title>
              </Panel.Heading>
              <Panel.Body collapsible>
                <ListGroup>
                  {recipe.ingredients.map((ingredient, index) => (
                    <ListGroupItem key={index}>{ingredient}</ListGroupItem>
                  ))}
                </ListGroup>
                <ButtonToolbar>
                  <Button bsStyle="warning" onClick={() => {this.showEditModal(index)}}>Edit</Button>
                  <Button bsStyle="danger">Delete</Button>
                </ButtonToolbar>
              </Panel.Body>
              <EditRecipe onShow={this.state.showEdit} onEdit={this.editRecipe} onEditModal={() => {this.showEditModal(this.state.currentlyEditing)}} currentlyEditing={this.state.currentlyEditing} recipe={recipes[this.state.currentlyEditing]} />
            </Panel>
          ))}
        </PanelGroup>
        <Button bsStyle="primary" onClick={this.showAddModal}>Add Recipe</Button>
        <AddRecipe onShow={this.state.showAdd} onAdd={this.addRecipe} onAddModal={this.showAddModal} />
      </div>
    );
  }
};

ReactDOM.render(<Recipe />, document.getElementById('app'));
ReactDOM.render(<Recipe />, document.getElementById('app'));

In editrecipe.js:

在editrecipe.js中:

//import the necessary files
import React from 'react';
import {Modal,ControlLabel,FormGroup,FormControl,Button} from 'react-bootstrap';

//create a class for displaying the modal for editing an existing recipe and export it
export class EditRecipe extends React.Component {
  constructor(props) {//create a state to handle the recipe to be edited
    super(props);
    this.state = {name: "", ingredients: ""};
    this.handleRecipeNameChange = this.handleRecipeNameChange.bind(this);
    this.handleRecipeIngredientsChange = this.handleRecipeIngredientsChange.bind(this);
    this.handleEdit = this.handleEdit.bind(this);
    this.handleCancel = this.handleCancel.bind(this);
  }
  static getDerivedStateFromProps(props, state) {//make the recipe prop a state
    const prevName = state.prevName;
    const prevIngredients = state.prevIngredients;
    const name = prevName !== props.recipe.name ? props.recipe.name : state.name;
    const ingredients = prevIngredients !== props.recipe.ingredients.join(",") ? props.recipe.ingredients.join(",") : state.ingredients;
    return {
      prevName: props.recipe.name, name,
      prevIngredients: props.recipe.ingredients.join(","), ingredients,
    }
  }
  handleRecipeNameChange(e) {//change the name to reflect user input
    this.setState({name: e.target.value});
  }
  handleRecipeIngredientsChange(e) {//change the ingredients to reflect user input
    this.setState({ingredients: e.target.value});
  }
  handleEdit(e) {//get the recipe data, manipulate it and call the function for editing an existing recipe
    e.preventDefault();
    const onEdit = this.props.onEdit;
    const currentlyEditing = this.props.currentlyEditing;
    const regExp = /\s*,\s*/;
    var name = this.state.name;
    var ingredients = this.state.ingredients.split(regExp);
    onEdit(name, ingredients, currentlyEditing);
  }
  handleCancel() {
    const onEditModal = this.props.onEditModal;
    this.setState({name: this.props.recipe.name, ingredients: this.props.recipe.ingredients.join(",")});
    onEditModal();
  }
  render() {
    const onShow = this.props.onShow;
    var regex1 = /^\S/;
    var regex2 = /^[^,\s]/;
    var regex3 = /[^,\s]$/;
    const validRecipe = regex1.test(this.state.name) && regex2.test(this.state.ingredients) && regex3.test(this.state.ingredients);
    return(
      <Modal show={onShow} onHide={this.handleCancel}>
        <Modal.Header closeButton>
          <Modal.Title>Edit Recipe</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <FormGroup controlId="formControlsName">
            <ControlLabel>Recipe Name</ControlLabel>
            <FormControl type="text" required onChange={this.handleRecipeNameChange} value={this.state.name} placeholder="Enter Name" />
          </FormGroup>
          <FormGroup controlId="formControlsIngredients">
            <ControlLabel>Recipe Ingredients</ControlLabel>
            <FormControl componentClass="textarea" type="text" required onChange={this.handleRecipeIngredientsChange} value={this.state.ingredients} placeholder="Enter Ingredients(separate by commas)" />
          </FormGroup>
        </Modal.Body>
        <Modal.Footer>
          <Button disabled={!validRecipe} bsStyle="success" onClick={this.handleEdit}>Save Recipe</Button>
        </Modal.Footer>
      </Modal>
    );
  }
};

Result:

结果:

In editRecipe.js, we create a state that holds the name and ingredients of the recipe to be edited, and set the initial values as empty strings. We then use React’s new life cycle method getDerivedStateFromProps to make our recipe prop’s name and ingredients the new name and ingredients of our state. The method for doing so is clearly explained here.

在editRecipe.js中,我们创建一个状态,其中包含要编辑的配方的名称和成分,并将初始值设置为空字符串。 然后,我们使用React的新生命周期方法getDerivedStateFromProps将配方道具的名称和成分设为状态的新名称和成分。 这样做的方法在这里已明确说明。

We will then change the state every time we change the contents of the form and validate the form as we did when adding a new recipe.

然后,我们将在每次更改表单内容时更改状态,并像添加新配方时一样对表单进行验证。

步骤6:创建Delete Recipe功能。 (Step 6: Creating the Delete Recipe function.)

We are now ready to delete recipes. This step does not need the creation of a new file.

现在我们准备删除配方。 此步骤不需要创建新文件。

To delete a recipe, a deleteRecipe() function that takes the argument index will be created. In this function, we use the index of a recipe to identify the recipe to be deleted. We will use JavaScript’s splice method to delete the recipe. We then set currentlyEditing to 0 just to reset the recipe box, that is, we don’t want currentlyEditing to still be the index of a recipe that doesn’t exist anymore.

要删除配方,将创建一个使用参数索引的deleteRecipe()函数。 在此功能中,我们使用配方的索引来标识要删除的配方。 我们将使用JavaScript的splice方法删除配方。 然后,我们将currentEditing设置为0只是为了重置配方框,也就是说,我们不希望currentEditing仍然是不再存在的配方的索引。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import {AddRecipe} from './components/addrecipe';
import {EditRecipe} from './components/editrecipe';
import './css/index.css';
//create the main class for displaying the recipes
class Recipe extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      recipes: [
        {name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},
        {name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},
        {name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}
      ],
      showAdd: false,
      showEdit: false,
      currentlyEditing: 0
    };
    this.showAddModal = this.showAddModal.bind(this);
    this.showEditModal = this.showEditModal.bind(this);
    this.addRecipe = this.addRecipe.bind(this);
    this.editRecipe = this.editRecipe.bind(this);
    this.deleteRecipe = this.deleteRecipe.bind(this);
  }
  showAddModal() {//show the new recipe modal
    this.setState({showAdd: !this.state.showAdd});
  }
  showEditModal(index) {//show the edit recipe modal
    this.setState({showEdit: !this.state.showEdit, currentlyEditing: index});
  }
  addRecipe(recipe) {//create a new recipe
    let recipes = this.state.recipes;
    recipes.push(recipe);
    this.setState({recipes: recipes});
    this.showAddModal();
  }
  editRecipe(newName, newIngredients, currentlyEditing) {//edit an existing recipe
    let recipes = this.state.recipes;
    recipes[currentlyEditing] = {name: newName, ingredients: newIngredients};
    this.setState({recipes: recipes});
    this.showEditModal(currentlyEditing);
  }
  deleteRecipe(index) {//delete an existing recipe
    let recipes = this.state.recipes.slice();
    recipes.splice(index, 1);
    this.setState({recipes: recipes, currentlyEditing: 0});
  }
  render() {
    const recipes = this.state.recipes;
    return(
      <div className="jumbotron">
        <h1>RECIPE BOX</h1>
        <PanelGroup accordion id="recipes">
          {recipes.map((recipe, index) => (
            <Panel eventKey={index} key={index}>
              <Panel.Heading>
                <Panel.Title className="title" toggle>{recipe.name}</Panel.Title>
              </Panel.Heading>
              <Panel.Body collapsible>
                <ListGroup>
                  {recipe.ingredients.map((ingredient, index) => (
                    <ListGroupItem key={index}>{ingredient}</ListGroupItem>
                  ))}
                </ListGroup>
                <ButtonToolbar>
                  <Button bsStyle="warning" onClick={() => {this.showEditModal(index)}}>Edit</Button>
                  <Button bsStyle="danger" onClick={() => {this.deleteRecipe(index)}}>Delete</Button>
                </ButtonToolbar>
              </Panel.Body>
              <EditRecipe onShow={this.state.showEdit} onEdit={this.editRecipe} onEditModal={() => {this.showEditModal(this.state.currentlyEditing)}} currentlyEditing={this.state.currentlyEditing} recipe={recipes[this.state.currentlyEditing]} />
            </Panel>
          ))}
        </PanelGroup>
        <Button bsStyle="primary" onClick={this.showAddModal}>Add Recipe</Button>
        <AddRecipe onShow={this.state.showAdd} onAdd={this.addRecipe} onAddModal={this.showAddModal} />
      </div>
    );
  }
};

ReactDOM.render(<Recipe />, document.getElementById('app'));

Result:

结果:

步骤7:添加本地存储。 (Step 7: Adding Local Storage.)

HTML 5 Web Storage allows web applications to store data locally within the user’s browser. There are two web storage objects:

HTML 5 Web存储允许Web应用程序在用户的浏览器中本地存储数据。 有两个Web存储对象:

(a) Session Storage: Session storage stores data for one session, and the data is lost when the browser tab is closed.

(a)会话存储:会话存储存储一个会话的数据,并且在关闭浏览器选项卡时数据会丢失。

(b) Local Storage: Local storage stores data indefinitely. The data will not be deleted when the browser is closed, and will be available all the time since there is no expiration date.

(b)本地存储:本地存储无限期地存储数据。 当浏览器关闭时,该数据将不会被删除,并且由于没有到期日期,因此将一直可用。

To add local storage, we will change our recipe state to an empty array. We will get the recipes from local storage first and then set our recipe state to these recipes. We will use the life cycle method componentDidMount, because we want to load the local storage after our component renders. We will also be updating local storage whenever we add, edit, or delete a recipe.

要添加本地存储,我们将配方状态更改为空数组。 我们将首先从本地存储中获取食谱,然后将我们的食谱状态设置为这些食谱。 我们将使用生命周期方法componentDidMount,因为我们想在组件渲染后加载本地存储。 每当我们添加,编辑或删除配方时,我们还将更新本地存储。

So if, for example, we delete one of our original 3 recipes and reload the page, we will not see the recipe we deleted. When we clear our local storage and reload the page, we will once again see the original recipe we deleted.

因此,例如,如果我们删除原始的3个食谱之一并重新加载页面,则不会看到删除的食谱。 当我们清除本地存储并重新加载页面后,我们将再次看到我们删除的原始配方。

In index.js:

在index.js中:

//import the necessary files
import React from 'react';
import ReactDOM from 'react-dom';
import {PanelGroup,Panel,Button,ButtonToolbar,ListGroup,ListGroupItem} from 'react-bootstrap';
import './css/index.css';
import {AddRecipe} from './components/addrecipe';
import {EditRecipe} from './components/editrecipe';
//create the main class for displaying the recipes
class Recipe extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      recipes: [],
      showAdd: false,
      showEdit: false,
      currentlyEditing: 0
    };
    this.showAddModal = this.showAddModal.bind(this);
    this.showEditModal = this.showEditModal.bind(this);
    this.addRecipe = this.addRecipe.bind(this);
    this.editRecipe = this.editRecipe.bind(this);
    this.deleteRecipe = this.deleteRecipe.bind(this);
  }
  componentDidMount() {//load the local storage data after the component renders
    var recipes = (typeof localStorage["recipes"] !== "undefined") ? JSON.parse(localStorage.getItem("recipes")) : [
      {name: "Banana Smoothie", ingredients: ["2 bananas", "1/2 cup vanilla yogurt", "1/2 cup skim milk", "2 teaspoons honey", "pinch of cinnamon"]},
      {name: "Spaghetti", ingredients: ["Noodles", "Tomato Sauce", "Meatballs"]},
      {name: "Split Pea Soup", ingredients: ["1 pound split peas", "1 onion", "6 carrots", "4 ounces of ham"]}
    ];
    this.setState({recipes: recipes});
  }
  showAddModal() {//show the new recipe modal
    this.setState({showAdd: !this.state.showAdd});
  }
  showEditModal(index) {//show the edit recipe modal
    this.setState({currentlyEditing: index, showEdit: !this.state.showEdit});
  }
  addRecipe(recipe) {//create a new recipe
    let recipes = this.state.recipes;
    recipes.push(recipe);
    localStorage.setItem('recipes', JSON.stringify(recipes));
    this.setState({recipes: recipes});
    this.showAddModal();
  }
  editRecipe(newName, newIngredients, currentlyEditing) {//edit an existing recipe
    let recipes = this.state.recipes;
    recipes[currentlyEditing] = {name: newName, ingredients: newIngredients};
    localStorage.setItem('recipes', JSON.stringify(recipes));
    this.setState({recipes: recipes});
    this.showEditModal(currentlyEditing);
  }
  deleteRecipe(index) {//delete an existing recipe
    let recipes = this.state.recipes.slice();
    recipes.splice(index, 1);
    localStorage.setItem('recipes', JSON.stringify(recipes));
    this.setState({recipes: recipes, currentlyEditing: 0});
  }
  render() {
    const recipes = this.state.recipes;
    var currentlyEditing = this.state.currentlyEditing;
    return(
      <div className="jumbotron">
        <h1>RECIPE BOX</h1>
        <PanelGroup accordion id="recipes">
          {recipes.map((recipe, index) => (
            <Panel eventKey={index} key={index}>
              <Panel.Heading>
                <Panel.Title className="title" toggle>{recipe.name}</Panel.Title>
              </Panel.Heading>
              <Panel.Body collapsible>
                <ListGroup>
                  {recipe.ingredients.map((ingredient, index) => (
                    <ListGroupItem key={index}>{ingredient}</ListGroupItem>
                  ))}
                </ListGroup>
                <ButtonToolbar>
                  <Button bsStyle="warning" onClick={() => {this.showEditModal(index)}}>Edit</Button>
                  <Button bsStyle="danger" onClick={() => {this.deleteRecipe(index)}}>Delete</Button>
                </ButtonToolbar>
              </Panel.Body>
              <EditRecipe onShow={this.state.showEdit} onEdit={this.editRecipe} onEditModal={() => {this.showEditModal(currentlyEditing)}} currentlyEditing={currentlyEditing} recipe={recipes[currentlyEditing]} />
            </Panel>
          ))}
        </PanelGroup>
        <Button bsStyle="primary" onClick={this.showAddModal}>Add Recipe</Button>
        <AddRecipe onShow={this.state.showAdd} onAdd={this.addRecipe} onAddModal={this.showAddModal} />
      </div>
    );
  }
};

ReactDOM.render(<Recipe />, document.getElementById('app'));

Result:

结果:

在GitHub上发布 (Posting on GitHub)

We are done making the recipe box. Time to post it on GitHub and create a GitHub page for it.

我们完成了配方框的制作。 是时候将其发布到GitHub并为其创建GitHub页面了。

On GitHub, create a new repository called recipe-box.

在GitHub上,创建一个新的存储库,称为“秘方盒”。

Go to your file directory on the command line and type the following:

在命令行上转到文件目录,然后键入以下内容:

git init
git add README.md
git commit -m "initial commit"
git remote add origin https://github.com/yourusername/recipe-box.git
git push -u origin master

Your code is now on GitHub. Now its time to create a GitHub page for the repository. This should be the current status of the package.json file:

您的代码现在在GitHub上。 现在该为存储库创建GitHub页面了。 这应该是package.json文件的当前状态:

On the command line we run:

在命令行上,我们运行:

npm install gh-pages --save-dev

GitHub pages will be installed. Then we must specify our “homepage” URL, and predeploy and deploy code in “scripts”, in package.json. The end result should be:

GitHub页面将被安装。 然后,我们必须指定我们的“主页” URL,并在package.json中的“脚本”中预部署和部署代码。 最终结果应为:

On the command line we run:

在命令行上,我们运行:

npm run deploy
git add .
git commit -m "created a github page for the repository"
git push origin master

We now have a GitHub page for the recipe box, and its URL is the one specified in “homepage” of package.json.

现在,我们为配方框提供了一个GitHub页面,其URL是package.json的“主页”中指定的URL。

The project is complete. For reference you can check out my GitHub repository here.

该项目已完成。 作为参考,您可以在此处查看我的GitHub存储库。

结论 (Conclusion)

This was certainly a thrilling challenge to tackle. I enjoyed sharing this with you. I hope you’ve learned something from it.

当然,这是一个艰巨的挑战。 我很高兴与您分享。 希望您从中学到了一些东西。

Thank you for reading.

感谢您的阅读。

翻译自: https://www.freecodecamp.org/news/how-to-build-freecodecamps-recipe-box-using-react-and-local-storage-3f285a96fe44/

react本地储存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值