博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
firebase使用_如何使用ReactJS和Firebase构建TodoApp
阅读量:2525 次
发布时间:2019-05-11

本文共 81465 字,大约阅读时间需要 271 分钟。

firebase使用

Hello folks, welcome to this tutorial. Before we begin you should be familiar with basic ReactJS concepts. If you're not, I would recommend that you go through the .

大家好,欢迎来到本教程 在开始之前,您应该熟悉基本的ReactJS概念。 如果不是这样,我建议您阅读 。

We will use the following components in this application:

我们将在此应用程序中使用以下组件:

我们的应用程序看起来如何: (How our application is going to look:)



应用架构: (Application Architecture:)

了解我们的组件: (Understanding our components:)

You may be wondering why we are using firebase in this application. Well, it provides secure Authentication, a Real-time database, a Serverless Component, and a Storage bucket.

您可能想知道为什么我们在此应用程序中使用Firebase。 好吧,它提供了安全的身份验证实时数据库无服务器组件存储桶

We are using Express here so that we don't need to handle HTTP Exceptions. We are going to use all the firebase packages in our functions component. This is because we don't want to make our client application too big, which tends to slow the loading process of the UI.

我们在这里使用Express,因此我们不需要处理HTTP异常。 我们将在function组件中使用所有firebase软件包。 这是因为我们不想使客户端应用程序太大,这会减慢UI的加载过程。

Note: I am going to divide this tutorial into four separate sections. At the start of every section, you will find a git commit that has the code developed in that section. Also If you want to see the complete code then it is available in this .

注意:我将本教程分为四个单独的部分。 在每个部分的开头,您将找到一个git commit,其中包含在该部分中开发的代码。 另外,如果您想查看完整的代码,则可以在此找到它。

第1节:开发Todo API (Section 1: Developing Todo APIs)

In this section, we are going to develop these elements:

在这个 部分我们将开发以下元素:

  1. Configure the firebase functions.

    配置Firebase功能。

  2. Install the Express framework and build Todo APIs.

    安装Express框架并构建Todo API。

  3. Configuring firestore as database.

    将Firestore配置为数据库。

The Todo API code implemented in this section can be found at this .

在此可以找到本节中实现的Todo API代码

配置Firebase功能: (Configure Firebase Functions:)

Go to the .

转到 。

Select the Add Project option. After that follow the gif down below step by step to configure the firebase project.

选择添加项目选项。 之后,按照下面的gif步骤逐步配置Firebase项目。

Go to the functions tab and click on the Get Started button:

转到功能选项卡,然后单击入门按钮:

You will see a dialogue box which has instructions on How to set up the Firebase Functions. Go to your local environment. Open a command-line tool. To install the firebase tools in your machine use the command below:

您将看到一个对话框,其中包含有关如何设置Firebase功能的说明 。 转到您的本地环境。 打开命令行工具。 要将Firebase工具安装到您的计算机中,请使用以下命令:

npm install -g firebase-tools

Once that is done then use the command firebase init to configure the firebase functions in your local environment. Select the following options when initialising the firebase function in the local environment:

完成后,请使用命令firebase init在您的本地环境中配置firebase功能。 在本地环境中初始化Firebase功能时,请选择以下选项:

  1. Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices => Functions: Configure and deploy Cloud Functions

    您要为此文件夹设置哪些Firebase CLI功能? 按空格键选择功能,然后按Enter确认您的选择=> 功能:配置和部署云功能

  2. First, let’s associate this project directory with a Firebase project …. => Use an existing project

    首先,让我们将此项目目录与Firebase项目关联起来。 =>使用现有项目

  3. Select a default Firebase project for this directory => application_name

    选择此目录的默认Firebase项目=> application_name

  4. What language would you like to use to write Cloud Functions? => JavaScript

    您想使用哪种语言编写Cloud Functions? => JavaScript

  5. Do you want to use ESLint to catch probable bugs and enforce style? => N

    您是否想使用ESLint捕获可能的错误并实施样式? => N

  6. Do you want to install dependencies with npm now? (Y/n) => Y

    您是否要立即使用npm安装依赖项? (Y / n)=> Y

After the configuration is done you will get the following message:

配置完成后,您将收到以下消息:

✔ Firebase initialization complete!

This will be our directory structure once the initialization is completed:

初始化完成后,这将是我们的目录结构:

+-- firebase.json +-- functions|   +-- index.js|   +-- node_modules|   +-- package-lock.json|   +-- package.json

Now open the index.js under functions directory and copy-paste the following code:

现在在函数目录下打开index.js并复制粘贴以下代码:

const functions = require('firebase-functions');exports.helloWorld = functions.https.onRequest((request, response) => {     response.send("Hello from Firebase!");});

Deploy the code to firebase functions using the following command:

使用以下命令将代码部署到firebase函数:

firebase deploy

Once the deployment is done you will get the following logline at the end of your command line:

部署完成后,您将在命令行末尾获得以下日志行:

> ✔  Deploy complete!> Project Console: https://console.firebase.google.com/project/todoapp-
/overview

Go to the Project Console > Functions and there you will find the URL of the API. The URL will look like this:

转到“ 项目控制台”>“功能” ,您将在其中找到API的URL。 该URL将如下所示:

https://
-todoapp-
.cloudfunctions.net/helloWorld

Copy this URL and paste it in the browser. You will get the following response:

复制此URL并将其粘贴到浏览器中。 您将收到以下响应:

Hello from Firebase!

This confirms that our Firebase function has been configured properly.

这确认我们的Firebase功能已正确配置。

安装Express框架: (Install the Express Framework:)

Now let’s install the Express framework in our project using the following command:

现在,使用以下命令在项目中安装Express框架:

npm i express

Now let's create an APIs directory inside the functions directory. Inside that directory, we will create a file named todos.js. Remove everything from the index.js and then copy-paste the following code:

现在,让我们在functions目录中创建一个APIs目录。 在该目录内,我们将创建一个名为todos.js的文件。 从index.js删除所有内容,然后复制粘贴以下代码:

//index.jsconst functions = require('firebase-functions');const app = require('express')();const {    getAllTodos} = require('./APIs/todos')app.get('/todos', getAllTodos);exports.api = functions.https.onRequest(app);

We have assigned the getAllTodos function to the /todos route. So all the API calls on this route will execute via the getAllTodos function. Now go to the todos.js file under APIs directory and here we will write the getAllTodos function.

我们已经将getAllTodos函数分配给/ todos路由。 因此,此路由上的所有API调用都将通过getAllTodos函数执行。 现在去todos.js的API目录下的文件,在这里我们将写getAllTodos功能。

//todos.jsexports.getAllTodos = (request, response) => {    todos = [        {            'id': '1',            'title': 'greeting',            'body': 'Hello world from sharvin shah'         },        {            'id': '2',            'title': 'greeting2',            'body': 'Hello2 world2 from sharvin shah'         }    ]    return response.json(todos);}

Here we have declared a sample JSON object. Later we will derive that from the Firestore. But for the time being we will return this. Now deploy this to your firebase function using the command firebase deploy. It will ask for permission to delete the module helloworld – just enter y.

在这里,我们声明了一个示例JSON对象。 稍后,我们将从Firestore中获得该信息。 但是暂时我们将返回此。 现在,使用命令firebase deploy到您的firebase函数。 它会问 获得删除模块helloworld的权限-只需输入y

The following functions are found in your project but do not exist in your local source code: helloWorldWould you like to proceed with deletion? Selecting no will continue the rest of the deployments. (y/N) y

Once this is done go to the Project Console > Functions and there you will find the URL of the API. The API will look like this:

完成此操作后,转到“ 项目控制台”>“功能” ,您将在其中找到API的URL。 该API将如下所示:

https://
-todoapp-
.cloudfunctions.net/api

Now go to the browser and copy-paste the URL and add /todos at the end of this URL. You will get the following output:

现在转到浏览器,复制粘贴URL,并在此URL的末尾添加/ todos 。 您将获得以下输出:

[        {            'id': '1',            'title': 'greeting',            'body': 'Hello world from sharvin shah'         },        {            'id': '2',            'title': 'greeting2',            'body': 'Hello2 world2 from sharvin shah'         }]

Firebase Firestore: (Firebase Firestore:)

We will use a firebase firestore as a real-time database for our application. Now go to the Console > Database in Firebase Console. To configure firestore follow the gif below:

我们将使用Firebase Firestore作为我们应用程序的实时数据库。 现在转到Firebase控制台中的控制台>数据库 。 要配置Firestore,请遵循以下gif:

Once the configuration is done then click on the Start Collection button and set Collection ID as todos. Click Next and you will get the following popup:

配置完成后,请单击“ 开始收集”按钮,并将“ 收集ID”设置为todos 。 单击下一步,您将获得以下弹出窗口:

Ignore the DocumentID key. For the field, type, and value, refer to the JSON down below. Update the value accordingly:

忽略DocumentID密钥。 对于字段,类型和值 ,请参考下面的JSON。 相应地更新值:

{    Field: title,    Type: String,    Value: Hello World},{    Field: body,    Type: String,    Value: Hello folks I hope you are staying home...},{    Field: createtAt,    type: timestamp,    value: Add the current date and time here}

Press the save button. You will see that the collection and the document is created. Go back to the local environment. We need to install firebase-admin which has the firestore package that we need. Use this command to install it:

按保存按钮。 您将看到集合和文档已创建。 回到本地环境。 我们需要安装具有我们所需的Firestore软件包的firebase-admin 。 使用以下命令进行安装:

npm i firebase-admin

Create a directory named util under the functions directory. Go to this directory and create a file name admin.js. In this file we will import the firebase admin package and initialize the firestore database object. We will export this so that other modules can use it.

功能目录下创建一个名为util的目录。 转到此目录并创建文件名admin.js 。 在此文件中,我们将导入firebase管理软件包并初始化firestore数据库对象。 我们将导出它,以便其他模块可以使用它。

//admin.jsconst admin = require('firebase-admin');admin.initializeApp();const db = admin.firestore();module.exports = { admin, db };

Now let’s write an API to fetch this data. Go to the todos.js under the functions > APIs directory. Remove the old code and copy-paste the code below:

现在,让我们编写一个API来获取此数据。 转到functions> APIs目录下的todos.js 。 删除旧代码,然后复制粘贴以下代码:

//todos.jsconst { db } = require('../util/admin');exports.getAllTodos = (request, response) => {	db		.collection('todos')		.orderBy('createdAt', 'desc')		.get()		.then((data) => {			let todos = [];			data.forEach((doc) => {				todos.push({                    todoId: doc.id,                    title: doc.data().title,					body: doc.data().body,					createdAt: doc.data().createdAt,				});			});			return response.json(todos);		})		.catch((err) => {			console.error(err);			return response.status(500).json({ error: err.code});		});};

Here we are fetching all the todos from the database and forwarding them to the client in a list.

在这里,我们从数据库中获取所有待办事项并将它们转发到列表中的客户端。

You can also run the application locally using firebase serve command instead of deploying it every time. When you run that command you may get an error regarding credentials. To fix it, follow the steps mentioned below:

您也可以使用firebase serve命令在本地运行该应用程序,而不必每次都部署它。 当您运行该命令时,可能会收到有关凭据的错误。 要修复它,请按照以下步骤操作:

  1. Go to the Project Settings (Settings icon at the top left-hand side)

    转到“ 项目设置” (左上角的“设置 ”图标)

  2. Go to the service accounts tab  

    转到服务帐户标签

  3. Down there will be the option of Generating a new key. Click on that option and it will download a file with a JSON extension.

    下方将有一个生成新密钥的选项。 单击该选项,它将下载带有JSON扩展名的文件。

  4. We need to export these credentials to our command line session. Use the command below to do that:

    我们需要将这些凭据导出到我们的命令行会话。 使用以下命令执行此操作:
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

After that run firebase serve command. If you still get the error then use the following command: firebase login --reauth. It will open the Google sign-in page in a browser. Once sign-in is done then it will work without any error.

之后,运行firebase serve命令。 如果仍然出现错误,请使用以下命令: firebase login --reauth 。 它将在浏览器中打开Goog​​le登录页面。 登录完成后,即可正常使用。

You will find a URL in the logs of your command-line tool when you run a firebase serve command. Open this URL in browser and append /todos after it.

运行firebase serve命令时,您会在命令行工具的日志中找到一个URL。 在浏览器中打开此URL,并在其后附加/todos

✔ functions[api]: http function initialized (http://localhost:5000/todoapp-
/
/api).

You will get the following JSON output in your browser:

您将在浏览器中获得以下JSON输出:

[    {        "todoId":"W67t1kSMO0lqvjCIGiuI",        "title":"Hello World",        "body":"Hello folks I hope you are staying home...",        "createdAt":{"_seconds":1585420200,"_nanoseconds":0 }    }]

编写其他API: (Writing Other APIs:)

It's time to write all the other todo APIs that we are going to require for our application.

是时候编写我们的应用程序需要的所有其他todo API了。

  1. Create Todo item: Go to the index.js under the functions directory. Import postOneTodo method under the existing getAllTodos. Also, assign the POST route to that method.

    创建Todo项:转到functions目录下的index.js 。 在现有getAllTodos下导入postOneTodo方法。 另外,将POST路由分配给该方法。

//index.jsconst {    ..,    postOneTodo} = require('./APIs/todos')app.post('/todo', postOneTodo);

Go to the todos.js inside the functions directory and add a new method postOneTodo under the existing getAllTodos method.

转至todos.js功能目录内,并添加了新的方法postOneTodo根据现行getAllTodos方法。

//todos.jsexports.postOneTodo = (request, response) => {	if (request.body.body.trim() === '') {		return response.status(400).json({ body: 'Must not be empty' });    }        if(request.body.title.trim() === '') {        return response.status(400).json({ title: 'Must not be empty' });    }        const newTodoItem = {        title: request.body.title,        body: request.body.body,        createdAt: new Date().toISOString()    }    db        .collection('todos')        .add(newTodoItem)        .then((doc)=>{            const responseTodoItem = newTodoItem;            responseTodoItem.id = doc.id;            return response.json(responseTodoItem);        })        .catch((err) => {			response.status(500).json({ error: 'Something went wrong' });			console.error(err);		});};

In this method, we are adding a new Todo to our database. If the elements of our body are empty then we will return a response of 400 or else we will add the data.

在这种方法中,我们向数据库中添加了一个新的Todo。 如果我们体内的元素为空,那么我们将返回400的响应,否则我们将添加数据。

Run the firebase serve command and open the postman application. Create a new request and select the method type as POST. Add the URL and a body of type JSON.

运行firebase serve命令并打开邮递员应用程序。 创建一个新请求并选择方法类型为POST 。 添加URL和JSON类型的正文。

URL: http://localhost:5000/todoapp-
/
/api/todoMETHOD: POSTBody: { "title":"Hello World", "body": "We are writing this awesome API"}

Press the send button and you will get the following response:

按下发送按钮,您将收到以下响应:

{     "title": "Hello World",     "body": "We are writing this awesome API",     "createdAt": "2020-03-29T12:30:48.809Z",     "id": "nh41IgARCj8LPWBYzjU0"}

2. Delete Todo item: Go to the index.js under the functions directory. Import the deleteTodo method under the existing postOneTodo. Also, assign the DELETE route to that method.

2. 删除待办事项:转到functions目录下的index.js 。 在现有的postOneTodo下导入deleteTodo方法。 另外,将DELETE路由分配给该方法。

//index.jsconst {    ..,    deleteTodo} = require('./APIs/todos')app.delete('/todo/:todoId', deleteTodo);

Go to the todos.js and add a new method deleteTodo under the existing postOneTodo method.

转至todos.js并添加新的方法deleteTodo现有下postOneTodo方法。

//todos.jsexports.deleteTodo = (request, response) => {    const document = db.doc(`/todos/${request.params.todoId}`);    document        .get()        .then((doc) => {            if (!doc.exists) {                return response.status(404).json({ error: 'Todo not found' })            }            return document.delete();        })        .then(() => {            response.json({ message: 'Delete successfull' });        })        .catch((err) => {            console.error(err);            return response.status(500).json({ error: err.code });        });};

In this method, we are deleting a Todo from our database. Run the firebase serve command and go to the postman. Create a new request, select the method type as DELETE and add the URL.

在这种方法中,我们将从数据库中删除待办事项。 运行firebase serve命令并转到邮递员。 创建一个新请求,选择方法类型为DELETE并添加URL。

URL: http://localhost:5000/todoapp-
/
/api/todo/
METHOD: DELETE

Press the send button and you will get the following response:

按下发送按钮,您将收到以下响应:

{   "message": "Delete successfull"}

3. Edit Todo item: Go to the index.js under the functions directory. Import the editTodo method under the existing deleteTodo. Also, assign the PUT route to that method.

3. 编辑待办事项:转到functions目录下的index.js 。 在现有deleteTodo下导入editTodo方法。 另外,将PUT路由分配给该方法。

//index.jsconst {    ..,    editTodo} = require('./APIs/todos')app.put('/todo/:todoId', editTodo);

Go to the todos.js and add a new method editTodo under the existing deleteTodo method.

转至todos.js并添加新的方法editTodo现有下deleteTodo方法。

//todos.jsexports.editTodo = ( request, response ) => {     if(request.body.todoId || request.body.createdAt){        response.status(403).json({message: 'Not allowed to edit'});    }    let document = db.collection('todos').doc(`${request.params.todoId}`);    document.update(request.body)    .then(()=> {        response.json({message: 'Updated successfully'});    })    .catch((err) => {        console.error(err);        return response.status(500).json({                 error: err.code         });    });};

In this method, we are editing a Todo from our database. Remember here we are not allowing the user to edit the todoId or createdAt fields. Run the firebase serve command and go to the postman. Create a new request, select the method type as PUT, and add the URL.

在这种方法中,我们正在从数据库中编辑Todo。 记住这里我们不允许用户编辑todoId或createdAt字段。 运行firebase serve命令并转到邮递员。 创建一个新请求,选择方法类型为PUT,然后添加URL。

URL: http://localhost:5000/todoapp-
/
/api/todo/
METHOD: PUT

Press the send button and you will get the following response:

按下发送按钮,您将收到以下响应:

{     "message": "Updated successfully"}

Directory structure till now:

到目前为止的目录结构:

+-- firebase.json +-- functions|   +-- API|   +-- +-- todos.js|   +-- util|   +-- +-- admin.js|   +-- index.js|   +-- node_modules|   +-- package-lock.json|   +-- package.json|   +-- .gitignore

With this, we have completed the first section of the application. You can go ahead have some coffee, take a break, and after that we will work on developing the User APIs.

至此,我们已经完成了应用程序的第一部分。 您可以继续喝咖啡,休息一下,然后,我们将开发用户API。

第2节:开发用户API (Section 2: Developing User APIs)

In this section, we are going to develop these components:

在这个 部分我们将开发以下组件:

  1. User Authentication ( Login and Signup ) API.

    用户身份验证(登录和注册)API。

  2. GET and Update user details API.

    获取和更新用户详细信息API。

  3. Update the user profile picture API.

    更新用户个人资料图片API。

  4. Securing the existing Todo API.

    保护现有的Todo API。

The User API code implemented in this section can be found at this .

在此可以找到在本节中实现的用户API代码。

So let’s start building the User Authentication API. Go to the Firebase console > Authentication.

因此,让我们开始构建用户身份验证API。 转到Firebase控制台>身份验证。

Click on the Set up sign-in-method button. We will use email and password for user validation. Enable the Email/Password option.

单击设置 登录方法按钮。 我们将使用电子邮件和密码进行用户验证。 启用电子邮件/密码选项。

Right now we will manually create our user. First, we will build the Login API. After that we will build the Sign-Up API.

现在,我们将手动创建用户。 首先,我们将构建Login API。 之后,我们将构建Sign-Up API。

Go to the Users Tab under Authentication, fill in the User details, and click on the Add User button.

转到“身份验证”下的“用户”选项卡,填写“用户详细信息”,然后单击“ 添加用户”按钮。

1.用户登录API: (1. User Login API:)

First, we need to install the firebase package, which consists of the Firebase Authentication library, using the following command:

首先,我们需要使用以下命令安装由Firebase身份验证库组成的firebase软件包:

npm i firebase

Once the installation is done go to the functions > APIs directory. Here we will create a users.js file. Now Inside index.js we import a loginUser method and assign the POST route to it.

安装完成后,转到functions> APIs目录。 在这里,我们将创建一个users.js文件。 现在在index.js内部,我们导入一个loginUser方法并为其分配POST路由。

//index.jsconst {    loginUser} = require('./APIs/users')// Usersapp.post('/login', loginUser);

Go to the Project Settings > General and there you will find the following card:

转到项目设置>常规 ,您将在其中找到以下卡片:

Select the Web Icon and then follow the gif down below:

选择Web图标,然后按照下面的gif进行操作:

Select the continue to console option. Once this is done you will see a JSON with firebase config. Go to the functions > util directory and create a  config.js file. Copy-paste the following code in this file:

选择继续控制台选项。 完成此操作后,您将看到带有firebase配置的JSON。 转到功能> util目录,并创建config.js文件。 将以下代码复制粘贴到此文件中:

// config.jsmodule.exports = {    apiKey: "............",    authDomain: "........",    databaseURL: "........",    projectId: ".......",    storageBucket: ".......",    messagingSenderId: "........",    appId: "..........",    measurementId: "......."};

Replace ............ with the values that you get under Firebase console > Project settings > General > your apps > Firebase SD snippet > config.

用在Firebase控制台>项目设置> 常规>应用程序> Firebase SD片段> config下获得的值替换............

Copy-paste the following code in the users.js file:

将以下代码复制并粘贴到users.js文件中:

// users.jsconst { admin, db } = require('../util/admin');const config = require('../util/config');const firebase = require('firebase');firebase.initializeApp(config);const { validateLoginData, validateSignUpData } = require('../util/validators');// Loginexports.loginUser = (request, response) => {    const user = {        email: request.body.email,        password: request.body.password    }    const { valid, errors } = validateLoginData(user);	if (!valid) return response.status(400).json(errors);    firebase        .auth()        .signInWithEmailAndPassword(user.email, user.password)        .then((data) => {            return data.user.getIdToken();        })        .then((token) => {            return response.json({ token });        })        .catch((error) => {            console.error(error);            return response.status(403).json({ general: 'wrong credentials, please try again'});        })};

Here we are using a firebase signInWithEmailAndPassword module to check if the user-submitted credentials are right. If they are right then we send the token of that user or else a 403 status with a "wrong credentials" message.

在这里,我们使用firebase signInWithEmailAndPassword模块来检查用户提交的凭据是否正确。 如果他们是正确的,则我们发送该用户的令牌或带有“错误凭证”消息的403状态。

Now let’s create validators.js under the functions > util directory. Copy-paste the following code in this file:

现在让我们创建validators.js下的功能> util目录。 将以下代码复制粘贴到此文件中:

// validators.jsconst isEmpty = (string) => {	if (string.trim() === '') return true;	else return false;};exports.validateLoginData = (data) => {   let errors = {};   if (isEmpty(data.email)) errors.email = 'Must not be empty';   if (isEmpty(data.password)) errors.password = 'Must not be  empty';   return {       errors,       valid: Object.keys(errors).length === 0 ? true : false    };};

With this our LoginAPI is completed. Run the firebase serve command and go to the postman. Create a new request, select the method type as POST, and add the URL and body.

这样,我们的LoginAPI就完成了。 运行firebase serve命令并转到邮递员。 创建一个新请求,选择方法类型为POST ,然后添加URL和正文。

URL: http://localhost:5000/todoapp-
/
/api/loginMETHOD: POSTBody: { "email":"Add email that is assigned for user in console", "password": "Add password that is assigned for user in console"}

Hit the send request button in postman and you will get the following output:

点击邮递员中的发送请求按钮,您将获得以下输出:

{       "token": ".........."}

We will use this token in an upcoming part to get the user details. Remember this token expires in 60 minutes. To generate a new token use this API again.

我们将在以后的部分中使用此令牌来获取用户详细信息 。 请记住,此令牌将在60分钟后过期。 要再次生成新令牌,请再次使用此API。

2.用户注册API: (2. User Sign-up API:)

The default authentication mechanism of firebase only allows you to store information like email, password, etc. But we need more information to identify if this user owns that todo so that they can perform read, update and delete operations on it.

firebase的默认身份验证机制仅允许您存储诸如电子邮件,密码等信息。但是我们需要更多信息来识别此用户是否拥有该待办事项,以便他们可以对其执行读取,更新和删除操作。

To achieve this goal we are going to create a new collection called users. Under this collection, we will store the user’s data which will be mapped to the todo based on the username. Each username will be unique for all the users on the platform.

为了实现此目标,我们将创建一个名为users的新集合。 在此集合下,我们将存储用户数据,该数据将根据用户名映射到待办事项。 每个用户名对于平台上的所有用户都是唯一的。

Go to the index.js. We import a signUpUser method and assign the POST route to it.

转到index.js 。 我们导入一个signUpUser方法并为其分配POST路由。

//index.jsconst {    ..,    signUpUser} = require('./APIs/users')app.post('/signup', signUpUser);

Now go to the validators.js and add the following code below the validateLoginData method.

现在去validators.js并添加下面的下面的代码validateLoginData方法。

// validators.jsconst isEmail = (email) => {	const emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;	if (email.match(emailRegEx)) return true;	else return false;};exports.validateSignUpData = (data) => {	let errors = {};	if (isEmpty(data.email)) {		errors.email = 'Must not be empty';	} else if (!isEmail(data.email)) {		errors.email = 'Must be valid email address';	}	if (isEmpty(data.firstName)) errors.firstName = 'Must not be empty';	if (isEmpty(data.lastName)) errors.lastName = 'Must not be empty';	if (isEmpty(data.phoneNumber)) errors.phoneNumber = 'Must not be empty';	if (isEmpty(data.country)) errors.country = 'Must not be empty';	if (isEmpty(data.password)) errors.password = 'Must not be empty';	if (data.password !== data.confirmPassword) errors.confirmPassword = 'Passowrds must be the same';	if (isEmpty(data.username)) errors.username = 'Must not be empty';	return {		errors,		valid: Object.keys(errors).length === 0 ? true : false	};};

Now go to the users.js and add the following code below the loginUser module.

现在转到users.js并在loginUser模块下面添加以下代码。

// users.jsexports.signUpUser = (request, response) => {    const newUser = {        firstName: request.body.firstName,        lastName: request.body.lastName,        email: request.body.email,        phoneNumber: request.body.phoneNumber,        country: request.body.country,		password: request.body.password,		confirmPassword: request.body.confirmPassword,		username: request.body.username    };    const { valid, errors } = validateSignUpData(newUser);	if (!valid) return response.status(400).json(errors);    let token, userId;    db        .doc(`/users/${newUser.username}`)        .get()        .then((doc) => {            if (doc.exists) {                return response.status(400).json({ username: 'this username is already taken' });            } else {                return firebase                        .auth()                        .createUserWithEmailAndPassword(                            newUser.email,                             newUser.password                    );            }        })        .then((data) => {            userId = data.user.uid;            return data.user.getIdToken();        })        .then((idtoken) => {            token = idtoken;            const userCredentials = {                firstName: newUser.firstName,                lastName: newUser.lastName,                username: newUser.username,                phoneNumber: newUser.phoneNumber,                country: newUser.country,                email: newUser.email,                createdAt: new Date().toISOString(),                userId            };            return db                    .doc(`/users/${newUser.username}`)                    .set(userCredentials);        })        .then(()=>{            return response.status(201).json({ token });        })        .catch((err) => {			console.error(err);			if (err.code === 'auth/email-already-in-use') {				return response.status(400).json({ email: 'Email already in use' });			} else {				return response.status(500).json({ general: 'Something went wrong, please try again' });			}		});}

We validate our user data, and after that we send an email and password to the firebase createUserWithEmailAndPassword module to create the user. Once the user is created successfully we save the user credentials in the database.

我们验证用户数据,然后将电子邮件和密码发送到firebase createUserWithEmailAndPassword模块以创建用户。 成功创建用户后,我们会将用户凭据保存在数据库中。

With this our SignUp API is completed. Run the firebase serve command and go to the postman. Create a new request, select the method type as POST. Add the URL and body.

至此,我们的SignUp API已完成。 运行firebase serve命令并转到邮递员。 创建一个新请求,选择方法类型为POST 。 添加URL和正文。

URL: http://localhost:5000/todoapp-
/
/api/signupMETHOD: POSTBody: { "firstName": "Add a firstName here", "lastName": "Add a lastName here", "email":"Add a email here", "phoneNumber": "Add a phone number here", "country": "Add a country here", "password": "Add a password here", "confirmPassword": "Add same password here", "username": "Add unique username here"}

Hit the send request button in postman and you will get the following Output:

点击邮递员中的发送请求按钮,您将获得以下输出:

{       "token": ".........."}

Now go to the Firebase console > Database and there you will see the following output:

现在转到Firebase控制台>数据库 ,在那里您将看到以下输出:

As you can see our user’s collection is successfully created with one document in it.

如您所见,我们的用户集合已成功创建,其中包含一个文档。

3.上传用户资料图片: (3. Upload User Profile Picture:)

Our users will be able to upload their profile picture. To achieve this we will be using Storage bucket. Go to the Firebase console > Storage and click on the Get started button. Follow the GIF below for the configuration:

我们的用户将能够上传他们的个人资料图片。 为此,我们将使用存储桶。 转到Firebase控制台>“存储” ,然后单击“入门”按钮。 请按照以下GIF进行配置:

Now go to the Rules tab under Storage and update the permission for the bucket access as per the image below:

现在转到存储下的“规则”选项卡,并根据下图更新存储桶访问权限:

To upload the profile picture we will be using the package named busboy. To install this package, use the following command:

要上传个人资料图片,我们将使用名为busboy的软件包。 要安装此软件包,请使用以下命令:

npm i busboy

Go to index.js. Import the uploadProfilePhoto method below the existing signUpUser method. Also assign the POST route to that method.

转到index.js 。 在现有signUpUser方法下方导入uploadProfilePhoto方法。 还要将POST路由分配给该方法。

//index.jsconst auth = require('./util/auth');const {    ..,    uploadProfilePhoto} = require('./APIs/users')app.post('/user/image', auth, uploadProfilePhoto);

Here we have added an authentication layer so that only a user associated with that account can upload the image. Now create a file named auth.js in functions > utils directory. Copy-paste the following code in that file:

在这里,我们添加了一个身份验证层,以便只有与该帐户关联的用户才能上传图像。 现在,在functions> utils目录中创建一个名为auth.js的文件。 将以下代码复制粘贴到该文件中:

// auth.jsconst { admin, db } = require('./admin');module.exports = (request, response, next) => {	let idToken;	if (request.headers.authorization && request.headers.authorization.startsWith('Bearer ')) {		idToken = request.headers.authorization.split('Bearer ')[1];	} else {		console.error('No token found');		return response.status(403).json({ error: 'Unauthorized' });	}	admin		.auth()		.verifyIdToken(idToken)		.then((decodedToken) => {			request.user = decodedToken;			return db.collection('users').where('userId', '==', request.user.uid).limit(1).get();		})		.then((data) => {			request.user.username = data.docs[0].data().username;			request.user.imageUrl = data.docs[0].data().imageUrl;			return next();		})		.catch((err) => {			console.error('Error while verifying token', err);			return response.status(403).json(err);		});};

Here we are using the firebase verifyIdToken module to verify the token. After that we are decoding the user details and passing them in the existing request.

在这里,我们使用firebase verifyIdToken模块来验证令牌。 之后,我们将解码用户详细信息并将其传递给现有请求。

Go to the users.js and add the following code below the signup method:

转到users.js并在signup方法下方添加以下代码:

// users.jsdeleteImage = (imageName) => {    const bucket = admin.storage().bucket();    const path = `${imageName}`    return bucket.file(path).delete()    .then(() => {        return    })    .catch((error) => {        return    })}// Upload profile pictureexports.uploadProfilePhoto = (request, response) => {    const BusBoy = require('busboy');	const path = require('path');	const os = require('os');	const fs = require('fs');	const busboy = new BusBoy({ headers: request.headers });	let imageFileName;	let imageToBeUploaded = {};	busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {		if (mimetype !== 'image/png' && mimetype !== 'image/jpeg') {			return response.status(400).json({ error: 'Wrong file type submited' });		}		const imageExtension = filename.split('.')[filename.split('.').length - 1];        imageFileName = `${request.user.username}.${imageExtension}`;		const filePath = path.join(os.tmpdir(), imageFileName);		imageToBeUploaded = { filePath, mimetype };		file.pipe(fs.createWriteStream(filePath));    });    deleteImage(imageFileName);	busboy.on('finish', () => {		admin			.storage()			.bucket()			.upload(imageToBeUploaded.filePath, {				resumable: false,				metadata: {					metadata: {						contentType: imageToBeUploaded.mimetype					}				}			})			.then(() => {				const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`;				return db.doc(`/users/${request.user.username}`).update({					imageUrl				});			})			.then(() => {				return response.json({ message: 'Image uploaded successfully' });			})			.catch((error) => {				console.error(error);				return response.status(500).json({ error: error.code });			});	});	busboy.end(request.rawBody);};

With this our Upload Profile Picture API is completed. Run the firebase serve command and go to the postman. Create a new request, select the method type as POST, add the URL, and in the body section select type as form-data.

这样就完成了我们的上传个人资料图片API 。 运行firebase serve命令并转到邮递员。 创建一个新请求,选择方法类型为POST ,添加URL,然后在主体部分中选择类型作为表单数据。

The request is protected so you’ll need to send the bearer token also. To send the bearer token, log in again if the token has expired. After that in Postman App > Authorization tab > Type > Bearer Token and in the token section paste the token.

该请求受到保护,因此您还需要发送承载令牌 。 要发送承载令牌,如果令牌已过期,请再次登录。 之后,在Postman App>授权标签>类型>承载令牌中,然后在令牌部分中粘贴令牌。

URL: http://localhost:5000/todoapp-
/
/api/user/imageMETHOD: GETBody: { REFER THE IMAGE down below }

Hit the send request button in postman and you will get the following Output:

点击邮递员中的发送请求按钮,您将获得以下输出:

{            "message": "Image uploaded successfully"}

4.获取用户详细信息: (4. Get User Details:)

Here we are fetching the data of our user from the database. Go to the index.js and import the getUserDetail method and assign GET route to it.

在这里,我们正在从数据库中获取用户的数据。 转到index.js并导入getUserDetail方法并为其分配GET路由。

// index.jsconst {    ..,    getUserDetail} = require('./APIs/users')app.get('/user', auth, getUserDetail);

Now go to the users.js and add the following code after the uploadProfilePhoto module:

现在转到users.js并在uploadProfilePhoto模块之后添加以下代码:

// users.jsexports.getUserDetail = (request, response) => {    let userData = {};	db		.doc(`/users/${request.user.username}`)		.get()		.then((doc) => {			if (doc.exists) {                userData.userCredentials = doc.data();                return response.json(userData);			}			})		.catch((error) => {			console.error(error);			return response.status(500).json({ error: error.code });		});}

We are using the firebase doc().get() module to derive the user details. With this our GET User Details API is completed. Run the firebase serve command and go to the postman. Create a new request, select the method type: GET, and add the URL and body.

我们正在使用firebase doc()。get()模块来导出用户详细信息。 这样,我们的GET User Details API就完成了。 运行firebase serve命令并转到邮递员。 创建一个新请求,选择方法类型: GET ,然后添加URL和正文。

The request is protected so you’ll need to send the bearer token also. To send the bearer token, log in again if the token has expired.

该请求受到保护,因此您还需要发送承载令牌 。 要发送承载令牌,如果令牌已过期,请再次登录。

URL: http://localhost:5000/todoapp-
/
/api/userMETHOD: GET

Hit the send request button in postman and you will get the following Output:

点击邮递员中的发送请求按钮,您将获得以下输出:

{   "userCredentials": {       "phoneNumber": "........",       "email": "........",       "country": "........",       "userId": "........",       "username": "........",       "createdAt": "........",       "lastName": "........",       "firstName": "........"    }}

5.更新用户详细信息: (5. Update user details:)

Now let’s add the functionality to update the user details. Go to the index.js and copy-paste the following code:

现在,让我们添加功能以更新用户详细信息。 转到index.js并复制粘贴以下代码:

// index.jsconst {    ..,    updateUserDetails} = require('./APIs/users')app.post('/user', auth, updateUserDetails);

Now go to the users.js and add the updateUserDetails module below the existing getUserDetails :

现在转到users.js并在现有getUserDetails下面添加updateUserDetails模块:

// users.jsexports.updateUserDetails = (request, response) => {    let document = db.collection('users').doc(`${request.user.username}`);    document.update(request.body)    .then(()=> {        response.json({message: 'Updated successfully'});    })    .catch((error) => {        console.error(error);        return response.status(500).json({             message: "Cannot Update the value"        });    });}

Here we are using the firebase update method. With this our Update User Details API is completed. Follow the same procedure for a request as with the Get User Details API above with one change. Add body in the request here and method as POST.

在这里,我们使用firebase 更新方法。 这样,我们的更新用户详细信息API就完成了。 遵循与上述“获取用户详细信息” API相同的请求步骤,但有一个更改。 在此处在请求中添加正文,并在方法中添加POST。

URL: http://localhost:5000/todoapp-
/
/api/userMETHOD: POSTBody : { // You can edit First Name, last Name and country // We will disable other Form Tags from our UI}

Hit the send request button in postman and you will get the following Output:

点击邮递员中的发送请求按钮,您将获得以下输出:

{    "message": "Updated successfully"}

6.保护Todo API: (6. Securing Todo APIs:)

To secure the Todo API so that only the chosen user can access it, we will make a few changes in our existing code. Firstly, we will update our index.js as follows:

为了保护Todo API,以便只有选定的用户才能访问它,我们将对现有代码进行一些更改。 首先,我们将如下更新index.js

// index.js// Todosapp.get('/todos', auth, getAllTodos);app.get('/todo/:todoId', auth, getOneTodo);app.post('/todo',auth, postOneTodo);app.delete('/todo/:todoId',auth, deleteTodo);app.put('/todo/:todoId',auth, editTodo);

We have updated all the Todo routes by adding auth so that all the API calls will require a token and can only be accessed by the particular user.

我们已经通过添加auth更新了所有Todo路由 ,以便所有API调用都需要一个令牌,并且只能由特定用户访问。

After that go to the todos.js under the functions > APIs directory.

之后,转到功能> API目录下的todos.js

  1. Create Todo API: Open the todos.js and under the postOneTodo method add the username key as follows:

    创建待办事项API:打开todos.jspostOneTodo方法下添加用户名重点如下:

const newTodoItem = {     ..,     username: request.user.username,     ..}

2. GET All Todos API: Open the todos.js and under the getAllTodos method add the where clause as follows:

2. 获取所有托多斯API:打开todos.js而根据getAllTodos方法添加where子句如下:

db.collection('todos').where('username', '==', request.user.username).orderBy('createdAt', 'desc')

Run the firebase serve and test our GET API. Don’t forget to send the bearer token. Here you will get a response error as follows:

运行firebase服务并测试我们的GET API。 不要忘记发送承载令牌。 在这里,您将收到如下响应错误:

{       "error": 9}

Go to the command line and you will see the following lines logged:

转到命令行,您将看到记录以下行:

i  functions: Beginning execution of "api">  Error: 9 FAILED_PRECONDITION: The query requires an index. You can create it here: 
> at callErrorFromStatus

Open this <URL> in the browser and click on create index.

在浏览器中打开此<URL> ,然后单击创建索引。

Once the index is built send the request again and you will get the following output:

建立索引后,再次发送请求,您将获得以下输出:

[   {      "todoId": "......",      "title": "......",      "username": "......",      "body": "......",      "createdAt": "2020-03-30T13:01:58.478Z"   }]

3.  Delete Todo API: Open the todos.js and under the deleteTodo method add the following condition. Add this condition inside the document.get().then() query below the !doc.exists condition.

3. 删除待办事项API:打开todos.jsdeleteTodo方法下添加以下条件。 将此条件添加到!doc.exists条件下面的document.get()。then()查询中。

..if(doc.data().username !== request.user.username){     return response.status(403).json({error:"UnAuthorized"})}

到目前为止的目录结构: (Directory structure up to now:)

+-- firebase.json +-- functions|   +-- API|   +-- +-- todos.js |   +-- +-- users.js|   +-- util|   +-- +-- admin.js|   +-- +-- auth.js|   +-- +-- validators.js|   +-- index.js|   +-- node_modules|   +-- package-lock.json|   +-- package.json|   +-- .gitignore

With this we have completed our API backend. Take a break, have a coffee, and after that we will start building the front end of our application

至此,我们已经完成了API后端。 休息一下,喝杯咖啡,然后我们将开始构建应用程序的前端

第3节:用户信息中心 (Section 3: User Dashboard)

In this section, we are going to develop these components:

在这个 部分我们将开发以下组件:

  1. Configure ReactJS and Material UI.

    配置ReactJS和Material UI。

  2. Building Login and SignUp Form.

    建立登录和注册表单。

  3. Building Account Section.

    建筑账户科。

The User Dashboard code implemented in this section can be found at this .

在此可以找到本节中实现的用户仪表板代码。

1.配置ReactJS和Material UI: (1. Configure ReactJS and Material UI:)

We will use the create-react-app template. It gives us a fundamental structure for developing the application. To install it, use the following command:

我们将使用create-react-app模板。 它为我们提供了开发应用程序的基本结构。 要安装它,请使用以下命令:

npm install -g create-react-app

Go to the root folder of the project where the functions directory is present. Initialize our front end application using the following command:

转到存在功能目录的项目的根文件夹。 使用以下命令初始化我们的前端应用程序:

create-react-app view

Remember to use version v16.13.1 of the ReactJS library.

切记使用的v16.13.1版本 ReactJS库

Once the installation is completed then you'll see the following in your command line logs:

安装完成后,您将在命令行日志中看到以下内容:

cd view  npm startHappy hacking!

With this, we have configured our React application. You’ll get the following directory structure:

这样,我们就配置了React应用程序。 您将获得以下目录结构:

+-- firebase.json +-- functions { This Directory consists our API logic }+-- view { This Directory consists our FrontEnd Compoenents }+-- .firebaserc+-- .gitignore

Now run the application using the command npm start . Go to the browser on and you’ll see the following output:

现在,使用命令npm start运行该应用程序。 转到上的浏览器,您将看到以下输出:

Now we will remove all the unnecessary components. Go to the view directory and then remove all the files which have [ Remove ] in front of them. For this, refer to the directory tree structure below.

现在,我们将删除所有不必要的组件。 转到视图目录,然后删除所有文件 前面有[删除]为此,请参考下面的目录树结构。

+-- README.md [ Remove ]+-- package-lock.json+-- package.json+-- node_modules+-- .gitignore+-- public|   +-- favicon.ico [ Remove ]|   +-- index.html|   +-- logo192.png [ Remove ]|   +-- logo512.png [ Remove ]|   +-- manifest.json|   +-- robots.txt+-- src|   +-- App.css|   +-- App.test.js|   +-- index.js|   +-- serviceWorker.js|   +-- App.js|   +-- index.css [ Remove ]|   +-- logo.svg [ Remove ]|   +-- setupTests.js

Go to index.html under the public directory and remove the following lines:

转到公共目录下的index.html并删除以下行:

Now go to the App.js under the src directory and replace the old code with the following code:

现在转到src目录下的App.js并将旧代码替换为以下代码:

import React from 'react';function App() {  return (    
);}export default App;

Go to the index.js and remove the following import:

转到index.js并删除以下导入:

import './index.css'

I have not deleted the App.css nor I am using it in this application. But if you want to delete or use it you are free to do that.

我尚未删除App.css也未在此应用程序中使用它。 但是,如果您要删除或使用它,则可以随意进行。

Go to the browser on and you’ll get a blank screen output.

转到上的浏览器,您将获得黑屏输出。

To install Material UI go to the view directory and copy-paste this command in the terminal:

要安装Material UI,请转到视图目录,然后在终端中复制并粘贴以下命令:

npm install @material-ui/core

Remember to use version v4.9.8 of the Material UI library.

记住要使用Material UI库的v4.9.8版本。

2.登录表格: (2. Login Form:)

To develop the login form go to App.js. At the top of App.js add the following imports:

要开发登录表单,请转到App.jsApp.js顶部添加以下导入:

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';import login from './pages/login';

We are using Switch and Route to assign routes for our TodoApp. Right now we will add only the /login route and assign a login component to it.

我们正在使用Switch and Route为TodoApp分配路由。 现在,我们将仅添加/ login路由并为其分配一个登录组件。

// App.js

Create a pages directory under the existing view directory and a file named login.js under the pages directory.

创建现有视图目录下的网页目录和文件名为login.js网页目录下。

We will import Material UI components and the Axios package in the login.js:

我们将在login.js导入Material UI组件和Axios包:

// login.js// Material UI componentsimport React, { Component } from 'react';import Avatar from '@material-ui/core/Avatar';import Button from '@material-ui/core/Button';import CssBaseline from '@material-ui/core/CssBaseline';import TextField from '@material-ui/core/TextField';import Link from '@material-ui/core/Link';import Grid from '@material-ui/core/Grid';import LockOutlinedIcon from '@material-ui/icons/LockOutlined';import Typography from '@material-ui/core/Typography';import withStyles from '@material-ui/core/styles/withStyles';import Container from '@material-ui/core/Container';import CircularProgress from '@material-ui/core/CircularProgress';import axios from 'axios';

We will add the following styles to our login page:

我们将以下样式添加到我们的登录页面:

// login.jsconst styles = (theme) => ({	paper: {		marginTop: theme.spacing(8),		display: 'flex',		flexDirection: 'column',		alignItems: 'center'	},	avatar: {		margin: theme.spacing(1),		backgroundColor: theme.palette.secondary.main	},	form: {		width: '100%',		marginTop: theme.spacing(1)	},	submit: {		margin: theme.spacing(3, 0, 2)	},	customError: {		color: 'red',		fontSize: '0.8rem',		marginTop: 10	},	progess: {		position: 'absolute'	}});

We will create a class named login which has a form and submit handler inside it.

我们将创建一个名为login的类,该类具有一个表单并在其中提交处理程序。

// login.jsclass login extends Component {	constructor(props) {		super(props);		this.state = {			email: '',			password: '',			errors: [],			loading: false		};	}	componentWillReceiveProps(nextProps) {		if (nextProps.UI.errors) {			this.setState({				errors: nextProps.UI.errors			});		}	}	handleChange = (event) => {		this.setState({			[event.target.name]: event.target.value		});	};	handleSubmit = (event) => {		event.preventDefault();		this.setState({ loading: true });		const userData = {			email: this.state.email,			password: this.state.password		};		axios			.post('/login', userData)			.then((response) => {				localStorage.setItem('AuthToken', `Bearer ${response.data.token}`);				this.setState({ 					loading: false,				});						this.props.history.push('/');			})			.catch((error) => {								this.setState({					errors: error.response.data,					loading: false				});			});	};	render() {		const { classes } = this.props;		const { errors, loading } = this.state;		return (			
Login
{"Don't have an account? Sign Up"}
{errors.general && (
{errors.general}
)}
); }}

At the end of this file add the following export:

在此文件的末尾添加以下导出:

export default withStyles(styles)(login);

Add our firebase functions URL to view > package.json as follows:

添加我们的firebase函数URL以查看> package.json ,如下所示:

Remember: Add a key named proxy below the existing browserslist JSON object

切记:在现有的browserslist JSON对象下面添加一个名为proxy的密钥

"proxy": "https://
-todoapp-
.cloudfunctions.net/api"

Install the Axios and material icon package using the following commands:

使用以下命令安装Axios材料图标包:

// Axios command:npm i axios// Material Icons:npm install @material-ui/icons

We have added a login route in App.js. In the login.js we have created a class component that handles the state, sends the post request to the login API using the Axios package. If the request is successful then we store the token. If we get errors in the response we simply render them on the UI.

我们在App.js添加了登录路径。 在login.js我们创建了一个处理状态的类组件,使用Axios包将发布请求发送到登录API。 如果请求成功,则我们存储令牌。 如果我们在响应中出现错误,则只需在UI上呈现它们即可。

Go to the browser at and you’ll see the following Login UI.

转到浏览器, ,您将看到以下登录UI。

Try filling wrong credentials or sending an empty request and you will get the errors. Send a valid request. Go to the Developer console > Application. You will see that users token is store in the Local storage. Once the Login is successful we will be routed back to the Home page.

尝试填写错误的凭据或发送空请求,您将收到错误。 发送有效的请求。 转到开发者控制台>应用程序 。 您将看到用户令牌存储在本地存储中。 登录成功后,我们将被路由回首页。

3.注册表格: (3. Signup Form:)

To develop the Signup form go to App.js and update the existing Route component with the line below:

要开发Signup表单,请转到App.js并使用以下代码行更新现有的Route组件:

// App.js

Don’t forget to import:

不要忘记导入:

// App.jsimport signup from './pages/signup';

Create a file named signup.js under the pages directory.

pages目录下创建一个名为signup.js的文件。

Inside the signup.js we will import the Material UI and Axios package:

在signup.js中,我们将导入Material UI和Axios包:

// signup.jsimport React, { Component } from 'react';import Avatar from '@material-ui/core/Avatar';import Button from '@material-ui/core/Button';import CssBaseline from '@material-ui/core/CssBaseline';import TextField from '@material-ui/core/TextField';import Link from '@material-ui/core/Link';import Grid from '@material-ui/core/Grid';import LockOutlinedIcon from '@material-ui/icons/LockOutlined';import Typography from '@material-ui/core/Typography';import Container from '@material-ui/core/Container';import withStyles from '@material-ui/core/styles/withStyles';import CircularProgress from '@material-ui/core/CircularProgress';import axios from 'axios';

We will add the following styles to our signup page:

我们将在注册页面中添加以下样式:

// signup.jsconst styles = (theme) => ({	paper: {		marginTop: theme.spacing(8),		display: 'flex',		flexDirection: 'column',		alignItems: 'center'	},	avatar: {		margin: theme.spacing(1),		backgroundColor: theme.palette.secondary.main	},	form: {		width: '100%', // Fix IE 11 issue.		marginTop: theme.spacing(3)	},	submit: {		margin: theme.spacing(3, 0, 2)	},	progess: {		position: 'absolute'	}});

We will create a class named signup which has a form and submit handler inside it.

我们将创建一个名为signup的类,该类具有一个表单并在其中提交处理程序。

// signup.jsclass signup extends Component {	constructor(props) {		super(props);		this.state = {			firstName: '',			lastName: '',			phoneNumber: '',			country: '',			username: '',			email: '',			password: '',			confirmPassword: '',			errors: [],			loading: false		};	}	componentWillReceiveProps(nextProps) {		if (nextProps.UI.errors) {			this.setState({				errors: nextProps.UI.errors			});		}	}	handleChange = (event) => {		this.setState({			[event.target.name]: event.target.value		});	};	handleSubmit = (event) => {		event.preventDefault();		this.setState({ loading: true });		const newUserData = {			firstName: this.state.firstName,			lastName: this.state.lastName,			phoneNumber: this.state.phoneNumber,			country: this.state.country,			username: this.state.username,			email: this.state.email,			password: this.state.password,			confirmPassword: this.state.confirmPassword		};		axios			.post('/signup', newUserData)			.then((response) => {				localStorage.setItem('AuthToken', `${response.data.token}`);				this.setState({ 					loading: false,				});					this.props.history.push('/');			})			.catch((error) => {				this.setState({					errors: error.response.data,					loading: false				});			});	};	render() {		const { classes } = this.props;		const { errors, loading } = this.state;		return (			
Sign up
Already have an account? Sign in
); }}

At the end of this file add the following export:

在此文件的末尾添加以下导出:

export default withStyles(styles)(signup);

The logic for the Signup component is the same as the login component. Go to the browser at and you’ll see the following Signup UI. Once the Signup is successful we will be routed back to the Home page.

注册组件的逻辑与登录组件的逻辑相同。 转到位于的浏览器,您将看到以下Signup UI。 注册成功后,我们将被路由回首页。

Try filling wrong credentials or sending an empty request and you will get the errors. Send a valid request. Go to the Developer console > Application. You will see that users token is store in the Local storage.

尝试填写错误的凭据或发送空请求,您将收到错误。 发送有效的请求。 转到开发者控制台>应用程序 。 您将看到用户令牌存储在本地存储中。

4.帐户部分: (4. Account Section:)

To build the account page we will need to first create our Home page from where we will load the account section. Go to the App.js and update the following route:

要构建帐户页面,我们需要首先从加载帐户部分的位置创建主页 。 转到App.js并更新以下路由:

// App.js

Don't forget the import:

不要忘记导入:

// App.jsimport home from './pages/home';

Create a new file named home.js . This file will be the index of our application. The Account and Todo sections both load on this page based on the button click.

创建一个名为home.js的新文件。 该文件将成为我们应用程序的索引。 “帐户”和“待办事项”部分均基于单击按钮加载到此页面上。

Import the Material UI packages, Axios package, our custom Account, todo components, and auth middleware.

导入Material UI包,Axios包,我们的自定义帐户,待办事项组件和auth中间件。

// home.jsimport React, { Component } from 'react';import axios from 'axios';import Account from '../components/account';import Todo from '../components/todo';import Drawer from '@material-ui/core/Drawer';import AppBar from '@material-ui/core/AppBar';import CssBaseline from '@material-ui/core/CssBaseline';import Toolbar from '@material-ui/core/Toolbar';import List from '@material-ui/core/List';import Typography from '@material-ui/core/Typography';import Divider from '@material-ui/core/Divider';import ListItem from '@material-ui/core/ListItem';import ListItemIcon from '@material-ui/core/ListItemIcon';import ListItemText from '@material-ui/core/ListItemText';import withStyles from '@material-ui/core/styles/withStyles';import AccountBoxIcon from '@material-ui/icons/AccountBox';import NotesIcon from '@material-ui/icons/Notes';import Avatar from '@material-ui/core/avatar';import ExitToAppIcon from '@material-ui/icons/ExitToApp';import CircularProgress from '@material-ui/core/CircularProgress';import { authMiddleWare } from '../util/auth'

We will set our drawerWidth as follows:

我们将如下设置抽屉宽度:

const drawerWidth = 240;

We will add the following style to our Home page:

我们将以下样式添加到我们的主页:

const styles = (theme) => ({	root: {		display: 'flex'	},	appBar: {		zIndex: theme.zIndex.drawer + 1	},	drawer: {		width: drawerWidth,		flexShrink: 0	},	drawerPaper: {		width: drawerWidth	},	content: {		flexGrow: 1,		padding: theme.spacing(3)	},	avatar: {		height: 110,		width: 100,		flexShrink: 0,		flexGrow: 0,		marginTop: 20	},	uiProgess: {		position: 'fixed',		zIndex: '1000',		height: '31px',		width: '31px',		left: '50%',		top: '35%'	},	toolbar: theme.mixins.toolbar});

We will create a class named home. This class will have an API call to get the User's profile picture, First name and Last name. Also it will have logic to choose which component to display, either Todo or Account:

我们将创建一个名为home的类。 此类将进行API调用以获取用户的个人资料图片,名字和姓氏。 它还具有选择要显示的组件(待办事项或帐户)的逻辑:

class home extends Component {	state = {		render: false	};	loadAccountPage = (event) => {		this.setState({ render: true });	};	loadTodoPage = (event) => {		this.setState({ render: false });	};	logoutHandler = (event) => {		localStorage.removeItem('AuthToken');		this.props.history.push('/login');	};	constructor(props) {		super(props);		this.state = {			firstName: '',			lastName: '',			profilePicture: '',			uiLoading: true,			imageLoading: false		};	}	componentWillMount = () => {		authMiddleWare(this.props.history);		const authToken = localStorage.getItem('AuthToken');		axios.defaults.headers.common = { Authorization: `${authToken}` };		axios			.get('/user')			.then((response) => {				console.log(response.data);				this.setState({					firstName: response.data.userCredentials.firstName,					lastName: response.data.userCredentials.lastName,					email: response.data.userCredentials.email,					phoneNumber: response.data.userCredentials.phoneNumber,					country: response.data.userCredentials.country,					username: response.data.userCredentials.username,					uiLoading: false,					profilePicture: response.data.userCredentials.imageUrl				});			})			.catch((error) => {				if(error.response.status === 403) {					this.props.history.push('/login')				}				console.log(error);				this.setState({ errorMsg: 'Error in retrieving the data' });			});	};	render() {		const { classes } = this.props;				if (this.state.uiLoading === true) {			return (				
{this.state.uiLoading &&
}
); } else { return (
TodoApp

{' '} {this.state.firstName} {this.state.lastName}

{' '}
{' '}
{' '}
{' '}
{' '}
{' '}
{this.state.render ?
:
}
); } }}

Here in the code, you will see that authMiddleWare(this.props.history); is used. This middleware checks if the authToken is null. If yes then it will push the user back to the login.js. This is added so that our user cannot access the / route without Signup or log in. At the end of this file add the following export:

在代码中,您将看到authMiddleWare(this.props.history); 用来。 该中间件检查authToken是否为null。 如果是,那么它将把用户推回到login.js 。 添加它是为了使我们的用户无需注册或登录就无法访问/路由。在此文件的末尾添加以下导出:

export default withStyles(styles)(home);

Now are you wondering what this code from home.js does?

现在您想知道home.js中的这段代码是做什么的吗?

{this.state.render ?
:
}

It is checking the render state which we are setting on the button click. Let's create the component directory, and under that directory create two files: account.js and todo.js.

它正在检查我们在单击按钮时设置的渲染状态。 让我们创建一个组件目录,并在该目录下创建两个文件: account.jstodo.js

Let’s create a directory named util and file named auth.js under that directory. Copy-paste the following code under auth.js :

我们创建一个名为util的目录,并在该目录下创建一个名为auth.js文件。 将以下代码复制粘贴到auth.js下:

export const authMiddleWare = (history) => {    const authToken = localStorage.getItem('AuthToken');    if(authToken === null){        history.push('/login')    }}

For time being inside the todo.js file we will just write a class which renders the text Hello I am todo. We will be working on our todos in the next section:

暂时在todo.js 文件,我们将只编写一个类,以呈现文本“ Hello I todo” 。 我们将在下一部分中研究待办事项:

import React, { Component } from 'react'import withStyles from '@material-ui/core/styles/withStyles';import Typography from '@material-ui/core/Typography';const styles = ((theme) => ({    content: {        flexGrow: 1,        padding: theme.spacing(3),    },    toolbar: theme.mixins.toolbar,    }));class todo extends Component {    render() {        const { classes } = this.props;        return (            
Hello I am todo
) }}export default (withStyles(styles)(todo));

Now it's time for the account section. Import the Material UI, clsx, axios and authmiddleWare utility in our account.js.

现在该到帐户部分了。 在我们的account.js导入Material UI,clsx,axios和authmiddleWare实用程序。

// account.jsimport React, { Component } from 'react';import withStyles from '@material-ui/core/styles/withStyles';import Typography from '@material-ui/core/Typography';import CircularProgress from '@material-ui/core/CircularProgress';import CloudUploadIcon from '@material-ui/icons/CloudUpload';import { Card, CardActions, CardContent, Divider, Button, Grid, TextField } from '@material-ui/core';import clsx from 'clsx';import axios from 'axios';import { authMiddleWare } from '../util/auth';

We will add the following styling to our Account page:

我们将在我们的“帐户”页面中添加以下样式:

// account.jsconst styles = (theme) => ({	content: {		flexGrow: 1,		padding: theme.spacing(3)	},	toolbar: theme.mixins.toolbar,	root: {},	details: {		display: 'flex'	},	avatar: {		height: 110,		width: 100,		flexShrink: 0,		flexGrow: 0	},	locationText: {		paddingLeft: '15px'	},	buttonProperty: {		position: 'absolute',		top: '50%'	},	uiProgess: {		position: 'fixed',		zIndex: '1000',		height: '31px',		width: '31px',		left: '50%',		top: '35%'	},	progess: {		position: 'absolute'	},	uploadButton: {		marginLeft: '8px',		margin: theme.spacing(1)	},	customError: {		color: 'red',		fontSize: '0.8rem',		marginTop: 10	},	submitButton: {		marginTop: '10px'	}});

We will create a class component named account. For the time being just copy-paste the following code:

我们将创建一个名为account的类组件。 目前,仅复制粘贴以下代码:

// account.jsclass account extends Component {	constructor(props) {		super(props);		this.state = {			firstName: '',			lastName: '',			email: '',			phoneNumber: '',			username: '',			country: '',			profilePicture: '',			uiLoading: true,			buttonLoading: false,			imageError: ''		};	}	componentWillMount = () => {		authMiddleWare(this.props.history);		const authToken = localStorage.getItem('AuthToken');		axios.defaults.headers.common = { Authorization: `${authToken}` };		axios			.get('/user')			.then((response) => {				console.log(response.data);				this.setState({					firstName: response.data.userCredentials.firstName,					lastName: response.data.userCredentials.lastName,					email: response.data.userCredentials.email,					phoneNumber: response.data.userCredentials.phoneNumber,					country: response.data.userCredentials.country,					username: response.data.userCredentials.username,					uiLoading: false				});			})			.catch((error) => {				if (error.response.status === 403) {					this.props.history.push('/login');				}				console.log(error);				this.setState({ errorMsg: 'Error in retrieving the data' });			});	};	handleChange = (event) => {		this.setState({			[event.target.name]: event.target.value		});	};	handleImageChange = (event) => {		this.setState({			image: event.target.files[0]		});	};	profilePictureHandler = (event) => {		event.preventDefault();		this.setState({			uiLoading: true		});		authMiddleWare(this.props.history);		const authToken = localStorage.getItem('AuthToken');		let form_data = new FormData();		form_data.append('image', this.state.image);		form_data.append('content', this.state.content);		axios.defaults.headers.common = { Authorization: `${authToken}` };		axios			.post('/user/image', form_data, {				headers: {					'content-type': 'multipart/form-data'				}			})			.then(() => {				window.location.reload();			})			.catch((error) => {				if (error.response.status === 403) {					this.props.history.push('/login');				}				console.log(error);				this.setState({					uiLoading: false,					imageError: 'Error in posting the data'				});			});	};	updateFormValues = (event) => {		event.preventDefault();		this.setState({ buttonLoading: true });		authMiddleWare(this.props.history);		const authToken = localStorage.getItem('AuthToken');		axios.defaults.headers.common = { Authorization: `${authToken}` };		const formRequest = {			firstName: this.state.firstName,			lastName: this.state.lastName,			country: this.state.country		};		axios			.post('/user', formRequest)			.then(() => {				this.setState({ buttonLoading: false });			})			.catch((error) => {				if (error.response.status === 403) {					this.props.history.push('/login');				}				console.log(error);				this.setState({					buttonLoading: false				});			});	};	render() {		const { classes, ...rest } = this.props;		if (this.state.uiLoading === true) {			return (				
{this.state.uiLoading &&
}
); } else { return (
{this.state.firstName} {this.state.lastName}
} className={classes.uploadButton} onClick={this.profilePictureHandler} > Upload Photo
{this.state.imageError ? (
{' '} Wrong Image Format || Supported Format are PNG and JPG
) : ( false )}
); } }}

At the end of this file add the following export:

在此文件的末尾添加以下导出:

export default withStyles(styles)(account);

In account.js there are lot of components used. First let's see how our application looks. After that I'll explain all the components that are used and why they are used.

account.js中,使用了许多组件。 首先,让我们看看我们的应用程序的外观。 之后,我将解释所有使用的组件以及使用它们的原因。

Go to the browser, and if your token is expired it will redirect you to the  login page. Add your details and log in again. Once you've done that, go to the Account tab and you will find the following UI:

转到浏览器,如果您的令牌已过期,它将把您重定向到login页面。 添加您的详细信息,然后再次登录。 完成此操作后,转到“帐户”标签,您将找到以下用户界面:

There are 3 handlers in the Account Section:

帐户部分中有3个处理程序:

  1. componentWillMount: This is React's inbuilt lifecycle method. We are using it to load the data before the render lifecycle and update our state values.

    componentWillMount :这是React的内置生命周期方法。 我们正在使用它在渲染生命周期之前加载数据并更新状态值。

  2. ProfilePictureUpdate: This is our custom handler that we are using so that when our user clicks on the Upload Photo button then it will send the data to a server and reload the page to show the user's new Profile Picture.

    ProfilePictureUpdate:这是我们正在使用的自定义处理程序,因此当用户单击“上载照片”按钮时,它将把数据发送到服务器并重新加载页面以显示用户的新“个人资料照片”。

  3. updateFormValues: This is also our custom handler to update the User's details. Here, the user can update their first name, last name, and country. We are not allowing email and username updates because our backend logic depends on those keys.

    updateFormValues:这也是我们用于更新用户详细信息的自定义处理程序。 在这里,用户可以更新他们的名字,姓氏和国家。 我们不允许电子邮件和用户名更新,因为我们的后端逻辑取决于这些键。

Other than these 3 handlers it is a form page with styling on top of it. Here is the directory structure up to this point inside the view folder:

除了这三个处理程序外,它还是一个表单页面,其顶部为样式。 这是view文件夹中到目前为止的目录结构:

+-- public +-- src|   +-- components|   +-- +-- todo.js|   +-- +-- account.js|   +-- pages|   +-- +-- home.js|   +-- +-- login.js|   +-- +-- signup.js|   +-- util|   +-- +-- auth.js |   +-- README.md|   +-- package-lock.json|   +-- package.json|   +-- .gitignore

With this we have completed our Account Dashboard. Now go have a coffee, take a break and in the next section, we will build the Todo Dashboard.

至此,我们已经完成了“帐户信息中心”。 现在去喝咖啡,休息一下,在下一节中,我们将构建Todo仪表板。

第4节:Todo资讯主页 (Section 4: Todo Dashboard)

In this section, we are going to develop the UI for these features of the Todos Dashboard:

在这个 部分我们将为Todos仪表板的以下功能开发UI:

  1. Add a Todo:

    添加待办事项:

  2. Get all todos:

    获取所有待办事项:

  3. Delete a todo

    删除待办事项

  4. Edit a todo

    编辑待办事项

  5. Get a todo

    得到一个待办事项

  6. Applying Theme

    应用主题

The Todo Dashboard code implemented in this section can be found at this .

在此可以找到本节中实现的Todo Dashboard代码。

Go to todos.js under the components directory. Add the following imports to the existing imports:

转到components目录下的todos.js 。 将以下导入添加到现有导入中:

import Button from '@material-ui/core/Button';import Dialog from '@material-ui/core/Dialog';import AddCircleIcon from '@material-ui/icons/AddCircle';import AppBar from '@material-ui/core/AppBar';import Toolbar from '@material-ui/core/Toolbar';import IconButton from '@material-ui/core/IconButton';import CloseIcon from '@material-ui/icons/Close';import Slide from '@material-ui/core/Slide';import TextField from '@material-ui/core/TextField';import Grid from '@material-ui/core/Grid';import Card from '@material-ui/core/Card';import CardActions from '@material-ui/core/CardActions';import CircularProgress from '@material-ui/core/CircularProgress';import CardContent from '@material-ui/core/CardContent';import MuiDialogTitle from '@material-ui/core/DialogTitle';import MuiDialogContent from '@material-ui/core/DialogContent';import axios from 'axios';import dayjs from 'dayjs';import relativeTime from 'dayjs/plugin/relativeTime';import { authMiddleWare } from '../util/auth';

We also need to add the following CSS elements in the existing style components:

我们还需要在现有样式组件中添加以下CSS元素:

const styles = (theme) => ({	.., // Existing CSS elements	title: {		marginLeft: theme.spacing(2),		flex: 1	},	submitButton: {		display: 'block',		color: 'white',		textAlign: 'center',		position: 'absolute',		top: 14,		right: 10	},	floatingButton: {		position: 'fixed',		bottom: 0,		right: 0	},	form: {		width: '98%',		marginLeft: 13,		marginTop: theme.spacing(3)	},	toolbar: theme.mixins.toolbar,	root: {		minWidth: 470	},	bullet: {		display: 'inline-block',		margin: '0 2px',		transform: 'scale(0.8)'	},	pos: {		marginBottom: 12	},	uiProgess: {		position: 'fixed',		zIndex: '1000',		height: '31px',		width: '31px',		left: '50%',		top: '35%'	},	dialogeStyle: {		maxWidth: '50%'	},	viewRoot: {		margin: 0,		padding: theme.spacing(2)	},	closeButton: {		position: 'absolute',		right: theme.spacing(1),		top: theme.spacing(1),		color: theme.palette.grey[500]	}});

We will add the transition for the pop up dialogue box:

我们将为弹出对话框添加过渡:

const Transition = React.forwardRef(function Transition(props, ref) {	return 
;});

Remove the existing todo class and copy-paste the following class:

删除现有的todo类并复制粘贴以下类:

class todo extends Component {	constructor(props) {		super(props);		this.state = {			todos: '',			title: '',			body: '',			todoId: '',			errors: [],			open: false,			uiLoading: true,			buttonType: '',			viewOpen: false		};		this.deleteTodoHandler = this.deleteTodoHandler.bind(this);		this.handleEditClickOpen = this.handleEditClickOpen.bind(this);		this.handleViewOpen = this.handleViewOpen.bind(this);	}	handleChange = (event) => {		this.setState({			[event.target.name]: event.target.value		});	};	componentWillMount = () => {		authMiddleWare(this.props.history);		const authToken = localStorage.getItem('AuthToken');		axios.defaults.headers.common = { Authorization: `${authToken}` };		axios			.get('/todos')			.then((response) => {				this.setState({					todos: response.data,					uiLoading: false				});			})			.catch((err) => {				console.log(err);			});	};	deleteTodoHandler(data) {		authMiddleWare(this.props.history);		const authToken = localStorage.getItem('AuthToken');		axios.defaults.headers.common = { Authorization: `${authToken}` };		let todoId = data.todo.todoId;		axios			.delete(`todo/${todoId}`)			.then(() => {				window.location.reload();			})			.catch((err) => {				console.log(err);			});	}	handleEditClickOpen(data) {		this.setState({			title: data.todo.title,			body: data.todo.body,			todoId: data.todo.todoId,			buttonType: 'Edit',			open: true		});	}	handleViewOpen(data) {		this.setState({			title: data.todo.title,			body: data.todo.body,			viewOpen: true		});	}	render() {		const DialogTitle = withStyles(styles)((props) => {			const { children, classes, onClose, ...other } = props;			return (				
{children}
{onClose ? (
) : null}
); }); const DialogContent = withStyles((theme) => ({ viewRoot: { padding: theme.spacing(2) } }))(MuiDialogContent); dayjs.extend(relativeTime); const { classes } = this.props; const { open, errors, viewOpen } = this.state; const handleClickOpen = () => { this.setState({ todoId: '', title: '', body: '', buttonType: '', open: true }); }; const handleSubmit = (event) => { authMiddleWare(this.props.history); event.preventDefault(); const userTodo = { title: this.state.title, body: this.state.body }; let options = {}; if (this.state.buttonType === 'Edit') { options = { url: `/todo/${this.state.todoId}`, method: 'put', data: userTodo }; } else { options = { url: '/todo', method: 'post', data: userTodo }; } const authToken = localStorage.getItem('AuthToken'); axios.defaults.headers.common = { Authorization: `${authToken}` }; axios(options) .then(() => { this.setState({ open: false }); window.location.reload(); }) .catch((error) => { this.setState({ open: true, errors: error.response.data }); console.log(error); }); }; const handleViewClose = () => { this.setState({ viewOpen: false }); }; const handleClose = (event) => { this.setState({ open: false }); }; if (this.state.uiLoading === true) { return (
{this.state.uiLoading &&
}
); } else { return (
{this.state.buttonType === 'Edit' ? 'Edit Todo' : 'Create a new Todo'}
{this.state.todos.map((todo) => (
{todo.title}
{dayjs(todo.createdAt).fromNow()}
{`${todo.body.substring(0, 65)}`}
))}
{this.state.title}
); } }}

At the end of this file add the following export:

在此文件的末尾添加以下导出:

export default withStyles(styles)(todo);

First we will understand how our UI works and after that we will understand the code. Go to the browser and you'll get the following UI:

首先,我们将了解我们的UI的工作原理,然后我们将了解代码。 转到浏览器,您将获得以下UI:

Click on the Add button at the bottom right corner and you’ll get the following screen:

单击右下角的添加按钮,您将获得以下屏幕:

Add the Todo title and details and press the submit button. You’ll get the following screen:

添加待办事项标题和详细信息,然后按提交按钮。 您将获得以下屏幕:

After this click on the view button and you’ll be able to see the  full details of the Todo:

单击查看按钮后,您将能够看到Todo的全部详细信息:

Click on the Edit button and you’ll be able to edit the todo:

单击“编辑”按钮,您将可以编辑待办事项:

Click the delete button and you’ll be able to delete the Todo. Now as we are aware of how Dashboard works, we will understand the components used in it.

点击删除按钮,即可删除待办事项。 现在,我们了解了Dashboard的工作原理,我们将了解其中使用的组件。

1. Add Todo: For implementing the add todo we will use the of Material UI. This component implements a hook functionality. We are using the classes so we will remove that functionality.

1.添加待办事项:为了实现添加待办事项,我们将使用Material UI的 。 该组件实现了挂钩功能。 我们正在使用这些类,因此我们将删除该功能。

// This sets the state to open and buttonType flag to add:const handleClickOpen = () => {      this.setState({           todoId: '',           title: '',           body: '',           buttonType: '',           open: true     });};// This sets the state to close:const handleClose = (event) => {      this.setState({ open: false });};

Other than this we will also change the placement of the Add Todo Button.

除此之外,我们还将更改“添加待办事项”按钮的位置。

// Position our buttonfloatingButton: {    position: 'fixed',    bottom: 0,    right: 0},

Now we will replace the list tag with a form inside this Dialogue. It will help us in adding the new todo.

现在,我们将使用此对话框中的表单替换列表标签。 这将有助于我们添加新的待办事项。

// Show Edit or Save depending on buttonType state{this.state.buttonType === 'Edit' ? 'Save' : 'Submit'}// Our Form to add a todo
// TextField here
// TextField here

The handleSubmit consists of logic to read the buttonType state. If the state is an empty string (“”) then it will post on the Add Todo API. If the state is an Edit then in that scenario it will update the Edit Todo.

handleSubmit 包含读取buttonType状态的逻辑。 如果状态为空字符串(“”)则它将发布在Add Todo API上。 如果状态为“ Edit则在那种​​情况下它将更新“编辑待办事项”。

2. Get Todos: To display the todos we will use the Grid container and inside it, we place the Grid item . Inside that, we will use a Card component to display the data.

2.获取待办事项:要显示待办事项,我们将使用Grid container并在其中放置Grid item 。 在其中,我们将使用Card组件显示数据。

{this.state.todos.map((todo) => (
// Here will show Todo with view, edit and delete button
))}

We use the map to display the todo item as the API sends them in a list. We will use the componentWillMount lifecycle to get and set the state before the render is executed. There are 3 buttons ( view, edit, and delete ) so we will need 3 Handlers to handle the operation when the button is clicked. We will learn about these buttons in their respective subsections.

当API将它们发送到列表中时,我们使用该地图显示待办事项。 我们将使用componentWillMount生命周期获取并设置执行渲染之前的状态。 一共有3个按钮( 查看,编辑和删除 ),因此当单击按钮时,我们将需要3个处理程序来处理操作。 我们将在各自的小节中了解这些按钮。

3. Edit Todo: For the edit todo, we are reusing the dialogue pop up code that is used in add todo. To differentiate between the button clicks we are using a buttonType state. For Add Todo the  buttonType state is (“”) while for edit todo it is Edit.

3.编辑待办事项:对于编辑待办事项,我们正在重用添加待办事项中使用的对话框弹出代码。 为了区分按钮单击,我们使用buttonType状态。 对于“添加待办事项”, buttonType状态为(“”)而对于要编辑的待办事项,状态为Edit

handleEditClickOpen(data) {	this.setState({		..,		buttonType: 'Edit',		..	});}

In the handleSubmit method we read the buttonType state and then send the request accordingly.

handleSubmit方法中,我们读取buttonType状态,然后相应地发送请求。

4. Delete Todo: When this button is clicked we send the todo object to our deleteTodoHandler and then it sends the request further to the backend.

4. Delete Todo:单击此按钮后,我们将todo对象发送到我们的deleteTodoHandler,然后将请求进一步发送到后端。

5. View Todo: When showing the data we have truncated it so that the user will get a glimpse of what the todo is about. But if a user wants to know more about it then they need to click on the view button.

5.查看待办事项:显示数据时,我们已将其截断,以便用户可以大致了解待办事项。 但是,如果用户想了解更多信息,则需要单击查看按钮。

For this, we will use the . Inside that, we use DialogTitle and DialogContent. It displays our title and content. In DialougeContent we will use the form to display the content that the user has posted. (This is one solution that I found there are many and you are free to try other.)

为此,我们将使用“ 。 在其中,我们使用DialogTitle和DialogContent。 它显示我们的标题和内容。 在DialougeContent中,我们将使用该表单显示用户已发布的内容。 (这是我发现有很多解决方案的一种,您可以自由尝试其他解决方案。)

// This is used to remove the underline of the FormInputProps={
{ disableUnderline: true}}// This is used so that user cannot edit the datareadonly

6. Applying Theme: This is the last step of our application. We will apply a theme on our application. For this we are using createMuiTheme and ThemeProvider from material UI. Copy-paste the following code in App.js:

6.应用主题:这是我们应用的最后一步。 我们将在应用程序上应用主题。 为此,我们使用了材料UI中的createMuiThemeThemeProvider 。 将以下代码复制粘贴到App.js

import { ThemeProvider as MuiThemeProvider } from '@material-ui/core/styles';import createMuiTheme from '@material-ui/core/styles/createMuiTheme';const theme = createMuiTheme({	palette: {		primary: {			light: '#33c9dc',			main: '#FF5722',			dark: '#d50000',			contrastText: '#fff'		}	}});function App() {	return (        
// Router and switch will be here.
);}

We missed applying a theme to our button in todo.js in the CardActions . Add the color tag for the view, edit, and delete button.

我们错过了在todo.js中将主题应用到todo.js中的CardActions 。 为查看,编辑和删除按钮添加颜色标签。

Go to the browser and you will find that everything is the same except that the app is a different color.

转到浏览器,您会发现除了应用程序颜色不同以外,其他所有内容都是相同的。

And we're done! We have built a TodoApp using ReactJS and Firebase. If you have built it all the way to this point then a very big congratulations to you on this achievement.

我们完成了! 我们已经使用ReactJS和Firebase构建了一个TodoApp。 如果您到现在为止都已经建立了它,那么非常感谢您取得这一成就。

Feel free to connect with me on and .

随时在和上与我联系。

翻译自:

firebase使用

转载地址:http://xhgwd.baihongyu.com/

你可能感兴趣的文章
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_3_BufferedInputStream_字节缓冲...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_06 Properties集合_1_使用Properties集合存储数据,遍历取出集合中的数据...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_4_缓冲流的效率测试_复制文件...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_06 Properties集合_3_Properties集合中的方法load...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_5_BufferedWriter_字符缓冲输出流...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_6_BufferedReader_字符缓冲输入流...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_07 缓冲流_7_练习_对文本的内容进行排序...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_08 转换流_1_字符编码和字符集...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_08 转换流_2_编码引出的问题_FileReader读取GBK格式文件...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_08 转换流_3_转换流的原理...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_08 转换流_4_OutputStreamWriter介绍&代码实现...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_08 转换流_5_InputStreamReader介绍&代码实现...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_08 转换流_6_练习_转换文件编码...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_09 序列化流_1_序列化和反序列化的概述...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_09 序列化流_5_InvalidClassException异常_原理...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_09 序列化流_2_对象的序列化流_ObjectOutputStream...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_10 打印流_1_打印流_概述和使用...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_09 序列化流_4_transient关键字_瞬态关键字...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_07-网络编程_第1节 网络通信概述_1_软件结构...
查看>>
阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_09 序列化流_6_练习_序列化集合...
查看>>