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 modalthis.setState({showAdd: !this.state.showAdd});}addRecipe(recipe) {//create a new recipelet 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 recipesuper(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 inputthis.setState({name: e.target.value});}handleRecipeIngredientsChange(e) {//change the ingredients to reflect user inputthis.setState({ingredients: e.target.value});}handleSubmit(e) {//get the recipe data, manipulate it and call the function for creating a new recipee.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 modalthis.setState({showAdd: !this.state.showAdd});}showEditModal(index) {//show the edit recipe modalthis.setState({showEdit: !this.state.showEdit, currentlyEditing: index});}addRecipe(recipe) {//create a new recipelet recipes = this.state.recipes;recipes.push(recipe);this.setState({recipes: recipes});this.showAddModal();}editRecipe(newName, newIngredients, currentlyEditing) {//edit an existing recipelet 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 editedsuper(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 stateconst 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 inputthis.setState({name: e.target.value});}handleRecipeIngredientsChange(e) {//change the ingredients to reflect user inputthis.setState({ingredients: e.target.value});}handleEdit(e) {//get the recipe data, manipulate it and call the function for editing an existing recipee.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 modalthis.setState({showAdd: !this.state.showAdd});}showEditModal(index) {//show the edit recipe modalthis.setState({showEdit: !this.state.showEdit, currentlyEditing: index});}addRecipe(recipe) {//create a new recipelet recipes = this.state.recipes;recipes.push(recipe);this.setState({recipes: recipes});this.showAddModal();}editRecipe(newName, newIngredients, currentlyEditing) {//edit an existing recipelet recipes = this.state.recipes;recipes[currentlyEditing] = {name: newName, ingredients: newIngredients};this.setState({recipes: recipes});this.showEditModal(currentlyEditing);}deleteRecipe(index) {//delete an existing recipelet 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 rendersvar 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 modalthis.setState({showAdd: !this.state.showAdd});}showEditModal(index) {//show the edit recipe modalthis.setState({currentlyEditing: index, showEdit: !this.state.showEdit});}addRecipe(recipe) {//create a new recipelet 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 recipelet 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 recipelet 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本地儲存

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/394163.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/394163.shtml
英文地址,請注明出處:http://en.pswp.cn/news/394163.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

調用接口返回500_公交卡余額查詢接口開放使用啦!

API說明本API返回數據僅支持JSON格式且會對中文進 行unicode 編碼&#xff0c;JSON格式返回數據基本格式如下&#xff1a;{"errCode": 0,"errMsg": "OK","data": {}}其中 errCode 表示請求狀態&#xff0c;0表示請求成功&#xff0c; …

stark組件開發之組合搜索基本顯示

數據的獲取&#xff0c;上一篇&#xff0c;已經有了&#xff01;然后就是&#xff0c;如何進行展示的問題。到了展示這里&#xff0c;又有了新的問題&#xff0c; 因為從數據庫&#xff0c;取得的數據。 分為 queryset 和 tuple 兩種數據結構。tuple 中&#xff0c;只是字符串。…

美國安全廠商在云安全上的最新進展

本文講的是美國安全廠商在云安全上的最新進展&#xff0c;【IT168 資訊】優利系統公司日前推出了一系列云產品和服務&#xff0c;并且著重強調企業創建私有云&#xff0c;公有云或混合云工具的安全。  Unisys Secure Cloud是優利系統公司推出的一種管理云服務&#xff0c;承諾…

hessianphp java_hessian 在PHP中的使用

一、hessian是什么&#xff1f;看到這個單詞我還不知道怎么讀&#xff0c;音標是[hes]讀黑森。Hessian是一個輕量級的遠程的數據交換工具&#xff0c;使用簡單的方法提供了RMI(遠程方法調用)的功能. 相比WebService&#xff0c;Hessian更簡單、快捷。采用的是二進制RPC協議&…

leetcode1025. 除數博弈(dp/數學)

愛麗絲和鮑勃一起玩游戲&#xff0c;他們輪流行動。愛麗絲先手開局。 最初&#xff0c;黑板上有一個數字 N 。在每個玩家的回合&#xff0c;玩家需要執行以下操作&#xff1a; 選出任一 x&#xff0c;滿足 0 < x < N 且 N % x 0 。 用 N - x 替換黑板上的數字 N 。 如…

100萬用戶服務器_我的應用在一個月內如何增長超過100萬用戶

100萬用戶服務器by Assaf Elovic通過阿薩夫埃洛維奇 我的應用在一個月內如何增長超過100萬用戶 (How my app grew by over 1M users in one month) 只需要這種簡單的每周方法和耐心。 (All it took was this simple weekly approach and patience.) Building and promoting a …

原生支付url參數錯誤_小程序支付

下載微信JSAPI支付的 SDK : https://pay.weixin.qq.com/wiki/doc/api/download/WxpayAPI_php.zip &#xff1b;解壓后放在extend 文件夾下&#xff0c;命名為wepay下載你的商戶證書&#xff0c;放在extend/wepay/cert/ 文件夾下面。自行將 extend/wepay/example/WxPay.Config.p…

Android清理設備內存具體完整演示樣例(二)

版權聲明&#xff1a; https://blog.csdn.net/lfdfhl/article/details/27672913 MainActivity例如以下: package cc.c;import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.List; import android.app.Activity; import a…

java圖片合成視頻_使用JAVACV把圖片合成視頻

使用JAVACV1.2把圖片合成視頻&#xff0c;直接上代碼。自己mark一下&#xff0c;也希望能夠幫助更多的人。package test;import static org.bytedeco.javacpp.opencv_imgcodecs.cvLoadImage;import java.io.File;import org.bytedeco.javacpp.avcodec;import org.bytedeco.java…

NPOI導出Excel

首先在官網去下載NPOI&#xff0c;把dll引用到項目中&#xff0c;然后獲取列表調用下面的方法就可以導出 后臺代碼&#xff1a; /// <summary> /// NPOI導出Excel /// </summary> /// <param name"dt"></param> /// <param name"fil…

leetcode1028. 從先序遍歷還原二叉樹(dfs/棧)

我們從二叉樹的根節點 root 開始進行深度優先搜索。 在遍歷中的每個節點處&#xff0c;我們輸出 D 條短劃線&#xff08;其中 D 是該節點的深度&#xff09;&#xff0c;然后輸出該節點的值。&#xff08;如果節點的深度為 D&#xff0c;則其直接子節點的深度為 D 1。根節點的…

react jest測試_如何使用Jest和react-testing-library測試Socket.io-client應用程序

react jest測試by Justice Mba由Mba法官 如何使用Jest和react-testing-library測試Socket.io-client應用程序 (How to test a Socket.io-client app using Jest and the react-testing-library) Testing the quality of real-time Socket.io-client integration seems to have…

統計學會用到python嗎_統計學學的統計軟件深嗎(例如Python)普通一本統計學大一不知道該干什么?...

統計學的話&#xff0c;不考慮把基礎課和專業課好好學一學嘛&#xff5e; 大一的話數分高代幾何已經占了很長時間啦&#xff0c;多刷刷題&#xff0c;把績點和排名搞得高一點是重中之重嘛&#xff5e;再說學習語言的事兒&#xff5e; 要說日常使用&#xff0c;那還是更推薦pyth…

枚舉轉中文,通過反射方法與描述的方式獲取

示例&#xff1a; 有人為了顯示中文&#xff0c;這樣定義枚舉嗎&#xff1f; publicenum TimeOfDay { 上午, 下午, 晚上 }; 這樣定義&#xff0c;很別扭&#xff0c;特別是在使用的時候&#xff0c; 比如&#xff0c;this.Time TimeOfDay.上午; 而…

Java語言最新實用案例教程_Java 語言實用案例教程

基本信息書名:Java 語言實用案例教程出版價格&#xff1a;48元作者:常玉慧, 王秀梅出版社&#xff1a;科學出版社出版日期&#xff1a;2016-10-1ISBN&#xff1a;9787030497383字數&#xff1a;387000頁碼&#xff1a;235版次&#xff1a;版裝幀&#xff1a;平裝開本&#xff1…

(轉)Java隨機數

1 隨機數的三種產生方式 本章先講解Java隨機數的幾種產生方式&#xff0c;然后通過示例對其進行演示。 廣義上講&#xff0c;Java中的隨機數的有三種產生方式&#xff1a; (01). 通過System.currentTimeMillis()來獲取一個當前時間毫秒數的long型數字。(02). 通過Math.random()…

leetcode105. 從前序與中序遍歷序列構造二叉樹(遞歸)

根據一棵樹的前序遍歷與中序遍歷構造二叉樹。注意: 你可以假設樹中沒有重復的元素。例如&#xff0c;給出前序遍歷 preorder [3,9,20,15,7] 中序遍歷 inorder [9,3,15,20,7] 返回如下的二叉樹&#xff1a;3/ \9 20/ \15 7代碼 /*** Definition for a binary tree node.*…

途虎養車三個創始人_3個來自非常規創始人的獲獎技術和產品見解

途虎養車三個創始人by Henry通過亨利 3個來自非常規創始人的獲獎技術和產品見解 (3 Winning Technology & Product Insights from WeChat’s unconventional founder) Intro: The writer is a current PMLinkedIn. Formerly he worked as a growth engineer Facebook. he …

Powershell-創建Module

1.找到默認module路徑&#xff0c;ISE啟動時自動加載默認領下的Module代碼。 $env:PSModulePath 2.在其中一個默認路徑下創建個文件夾&#xff0c;在文件夾下創建一個.psm1后綴文件&#xff0c;注意文件夾名字與文件名一樣。 3.在.psm1文件中寫入函數代碼。 4.重啟ISE自動加載m…

android是java_為什么大家都用JAVA寫android程序

您好&#xff0c;1、原始類型&#xff1a;v void 只能用于返回值類型Z booleanB byteS shortC charI intJ long(64位)F floatD double(64位)對象類型&#xff1a;Lpackage/name/ObjectName相當于java中的package.name.ObjectName解釋如下&#xff1a;L&#xff1a;表示這是一個…