ReactJSとFirebaseを使用してTodoAppを構築する方法

みなさん、こんにちは。このチュートリアルへようこそ。始める前に、ReactJSの基本的な概念に精通している必要があります。そうでない場合は、ReactJSのドキュメントを確認することをお勧めします。

このアプリケーションでは、次のコンポーネントを使用します。

  1. ReactJS
  2. マテリアルUI
  3. Firebase
  4. ExpressJS
  5. 郵便配達員

アプリケーションの外観:

アプリケーションアーキテクチャ:

コンポーネントを理解する:

このアプリケーションでFirebaseを使用している理由を疑問に思われるかもしれません。それは、安全な認証リアルタイムデータベースサーバーレスコンポーネント、およびストレージバケットを提供します。

ここではExpressを使用しているため、HTTP例外を処理する必要はありません。関数コンポーネントですべてのfirebaseパッケージを使用します。これは、クライアントアプリケーションを大きくしすぎて、UIの読み込みプロセスが遅くなる傾向があるためです。

注:このチュートリアルを4つのセクションに分けます。すべてのセクションの開始時に、そのセクションで開発されたコードを含むgitcommitがあります。また、完全なコードを確認したい場合は、このリポジトリで入手できます。

セクション1:TodoAPIの開発

これでセクションこれらの要素を開発します。

  1. Firebase機能を設定します。
  2. Expressフレームワークをインストールし、TodoAPIをビルドします。
  3. ファイヤーストアをデータベースとして構成します。

藤堂APIコードこのコミットでは、このセクションで実装を見つけることができます。

Firebase機能の設定:

Firebaseコンソールに移動します。

[プロジェクト追加]オプションを選択します。その後、以下のgifに従って、Firebaseプロジェクトを構成します。

[関数]タブに移動し、[開始]ボタンをクリックします。

Firebase関数の設定方法を説明するダイアログボックスが表示されます。ローカル環境に移動します。コマンドラインツールを開きます。マシンにFirebaseツールをインストールするには、次のコマンドを使用します。

 npm install -g firebase-tools

それが完了したら、コマンドfirebase initを使用してローカル環境でFirebase機能を設定します。ローカル環境でFirebase機能を初期化する場合は、次のオプションを選択してください。

  1. このフォルダにどのFirebaseCLI機能を設定しますか?Spaceを押して機能を選択し、Enterキーを押して選択を確認します=>機能:CloudFunctionsを構成してデプロイします
  2. まず、このプロジェクトディレクトリをFirebaseプロジェクトに関連付けましょう…。=>既存のプロジェクトを使用する
  3. このディレクトリのデフォルトのFirebaseプロジェクトを選択します=> application_name
  4. Cloud Functionsの作成にどの言語を使用しますか?=> JavaScript
  5. ESLintを使用して、考えられるバグをキャッチし、スタイルを適用しますか?=> N
  6. 今すぐnpmで依存関係をインストールしますか?(Y / n)=> Y

構成が完了すると、次のメッセージが表示されます。

✔ Firebase initialization complete!

初期化が完了すると、これがディレクトリ構造になります。

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

次に、index.jsunder関数ディレクトリを開き、次のコードをコピーして貼り付けます。

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

次のコマンドを使用して、コードをFirebase関数にデプロイします。

firebase deploy

展開が完了すると、コマンドラインの最後に次のログラインが表示されます。

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

プロジェクトコンソール>関数に移動すると、APIのURLが表示されます。URLは次のようになります。

//-todoapp-.cloudfunctions.net/helloWorld

このURLをコピーして、ブラウザに貼り付けます。次の応答が返されます。

Hello from Firebase!

これにより、Firebase機能が正しく設定されていることが確認されます。

ExpressFrameworkをインストールします。

次にExpress、次のコマンドを使用して、プロジェクトにフレームワークをインストールしましょう。

npm i express

それでは、functionsディレクトリ内にAPIディレクトリを作成しましょう。そのディレクトリ内に、という名前のファイルを作成します。からすべてを削除してから、次のコードをコピーして貼り付けます。todos.jsindex.js

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

getAllTodos関数を/ todosルートに割り当てました。したがって、このルートでのすべてのAPI呼び出しは、getAllTodos関数を介して実行されます。次に、todos.jsAPIディレクトリの下のファイルに移動し、ここでgetAllTodos関数を記述します。

//todos.js exports.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); }

ここでは、サンプルのJSONオブジェクトを宣言しました。後でそれをFirestoreから導き出します。しかし、当分の間、これを返します。次に、コマンドを使用してこれをfirebase関数にデプロイしますfirebase deploy。それは尋ねますモジュールhelloworldを削除する権限については、yと入力するだけです。

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

これが完了したら、[プロジェクトコンソール]> [関数]に移動すると、APIのURLが表示されます。APIは次のようになります。

//-todoapp-.cloudfunctions.net/api

次に、ブラウザに移動して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:

アプリケーションのリアルタイムデータベースとして、firebasefirestoreを使用します。次に、FirebaseConsoleの[コンソール]> [データベース]に移動します。ファイヤーストアを構成するには、以下のgifに従ってください。

構成が完了したら、[収集開始]ボタンをクリックし、コレクションIDtodosとして設定します。[次へ]をクリックすると、次のポップアップが表示されます。

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 }

保存ボタンを押します。コレクションとドキュメントが作成されていることがわかります。ローカル環境に戻ります。必要なfirebase-adminfirestoreパッケージを含むをインストールする必要があります。次のコマンドを使用してインストールします。

npm i firebase-admin

関数ディレクトリの下にutilという名前のディレクトリを作成します。このディレクトリに移動し、ファイル名を作成しますadmin.js。このファイルでは、firebase adminパッケージをインポートし、firestoreデータベースオブジェクトを初期化します。他のモジュールが使用できるように、これをエクスポートします

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

次に、このデータをフェッチするAPIを作成しましょう。関数> APIディレクトリのtodos.js下に移動します。古いコードを削除し、以下のコードをコピーして貼り付けます。

//todos.js const { 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}); }); };

ここでは、データベースからすべてのToDoをフェッチし、リスト内のクライアントに転送しています。

firebase serve毎回デプロイする代わりに、コマンドを使用してアプリケーションをローカルで実行することもできます。そのコマンドを実行すると、資格情報に関するエラーが発生する場合があります。これを修正するには、以下の手順に従ってください。

  1. 移動し、プロジェクトの設定(左上側の設定アイコン)
  2. [サービスアカウント]タブに移動します  
  3. 下に、新しいキー生成するオプションがあります。そのオプションをクリックすると、JSON拡張子のファイルがダウンロードされます。
  4. これらの資格情報をコマンドラインセッションにエクスポートする必要があります。これを行うには、以下のコマンドを使用します。
export GOOGLE_APPLICATION_CREDENTIALS="/home/user/Downloads/[FILE_NAME].json"

その後、firebaseserveコマンドを実行します。それでもエラーが発生する場合は、次のコマンドを使用してくださいfirebase login --reauth。ブラウザでGoogleサインインページが開きます。サインインが完了すると、エラーなしで機能します。

Firebase Serveコマンドを実行すると、コマンドラインツールのログにURLが表示されます。このURLをブラウザで開き、/todosその後に追加します。

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

ブラウザに次のJSON出力が表示されます。

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

他のAPIの作成:

アプリケーションに必要な他のすべてのtodoAPIを作成するときが来ました。

  1. Todoアイテムの作成:index.js functionsディレクトリの下に移動します。既存のgetAllTodosの下にpostOneTodoメソッドをインポートします。また、POSTルートをそのメソッドに割り当てます。
//index.js const { .., postOneTodo } = require('./APIs/todos') app.post('/todo', postOneTodo);

todos.js関数ディレクトリ内に移動し、postOneTodo既存のgetAllTodosメソッドの下に新しいメソッドを追加します。

//todos.js exports.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); }); };

この方法では、データベースに新しいTodoを追加します。ボディの要素が空の場合は400の応答を返すか、データを追加します。

firebaseserveコマンドを実行してpostmanアプリケーションを開きます。新しいリクエストを作成し、メソッドタイプをPOSTとして選択します。URLとJSONタイプの本文を追加します。

URL: //localhost:5000/todoapp-//api/todo METHOD: POST Body: { "title":"Hello World", "body": "We are writing this awesome API" }

送信ボタンを押すと、次の応答が返されます。

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

2. Todoアイテムの削除:index.js functionsディレクトリの下に移動します。既存のpostOneTodoの下にdeleteTodoメソッドをインポートします。また、そのメソッドにDELETEルートを割り当てます。

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

に移動し、既存のメソッドの下にtodos.js新しいメソッドを追加します。deleteTodopostOneTodo

//todos.js exports.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 }); }); };

この方法では、データベースからTodoを削除します。firebaseserveコマンドを実行して郵便配達員に移動します。新しいリクエストを作成し、メソッドタイプをDELETEとして選択して、URLを追加します。

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

送信ボタンを押すと、次の応答が返されます。

{ "message": "Delete successfull" }

3. Todoアイテムの編集:index.js functionsディレクトリの下に移動します。既存のdeleteTodoの下にeditTodoメソッドをインポートします。また、PUTルートをそのメソッドに割り当てます。

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

に移動し、既存のメソッドの下にtodos.js新しいメソッドを追加します。editTododeleteTodo

//todos.js exports.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 }); }); };

この方法では、データベースからTodoを編集しています。ここでは、ユーザーがtodoIdフィールドまたはcreatedAtフィールドを編集することを許可していないことに注意してください。firebaseserveコマンドを実行して郵便配達員に移動します。新しいリクエストを作成し、メソッドタイプをPUTとして選択して、URLを追加します。

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

送信ボタンを押すと、次の応答が返されます。

{ "message": "Updated successfully" }

これまでのディレクトリ構造:

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

これで、アプリケーションの最初のセクションが完了しました。コーヒーを飲みながら休憩してから、ユーザーAPIの開発に取り組みます。

セクション2:ユーザーAPIの開発

これでセクション次のコンポーネントを開発します。

  1. ユーザー認証(ログインとサインアップ)API。
  2. ユーザー詳細APIを取得して更新します。
  3. ユーザープロファイル画像APIを更新します。
  4. 既存のTodoAPIを保護します。

このセクションで実装されているユーザーAPIコードは、このコミットで見つけることができます。

それでは、ユーザー認証APIの構築を始めましょう。行くFirebaseコンソール>認証。

クリックしてセットアップサインイン-方法ボタンを押します。ユーザーの検証にはメールアドレスとパスワードを使用します。電子メール/パスワードオプションを有効にします。

今、手動でユーザーを作成します。まず、LoginAPIを構築します。その後、サインアップAPIを構築します。

[認証]の下の[ユーザー]タブに移動し、ユーザーの詳細を入力して、[ユーザー追加]ボタンをクリックします。

1.ユーザーログインAPI:

まず、次のコマンドを使用してfirebaseFirebaseAuthenticationライブラリで構成されるパッケージをインストールする必要があります。

npm i firebase

インストールが完了したら、functions> APIsディレクトリに移動します。ここでusers.jsファイルを作成します。ここでindex.js、loginUserメソッドをインポートし、それにPOSTルートを割り当てます。

//index.js const { loginUser } = require('./APIs/users') // Users app.post('/login', loginUser);

[プロジェクト設定]> [一般]に移動すると、次のカードが見つかります。

Webアイコンを選択し、下のgifに従ってください。

[コンソールに進む]オプションを選択します。これが完了すると、firebaseconfigを含むJSONが表示されます。関数> utilディレクトリに移動し、config.jsファイルを作成し  ます。このファイルに次のコードをコピーして貼り付けます。

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

Firebaseコンソール>プロジェクト設定>一般>アプリ> FirebaseSDスニペット>設定で............取得した値に置き換えます

次のコードをコピーしてusers.jsファイルに貼り付けます。

// users.js const { admin, db } = require('../util/admin'); const config = require('../util/config'); const firebase = require('firebase'); firebase.initializeApp(config); const { validateLoginData, validateSignUpData } = require('../util/validators'); // Login exports.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'}); }) };

ここでは、firebase signInWithEmailAndPasswordモジュールを使用して、ユーザーが送信した認証情報が正しいかどうかを確認しています。それらが正しい場合は、そのユーザーのトークンを送信するか、「間違った資格情報」メッセージを含む403ステータスを送信します。

それではvalidators.jsfunctions> utilディレクトリの下に作成しましょう。このファイルに次のコードをコピーして貼り付けます。

// validators.js const 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 }; };

これでLoginAPIが完成します。firebase serveコマンドを実行して、郵便配達員に移動します。新しいリクエストを作成し、メソッドタイプをPOSTとして選択し、URLと本文を追加します。

URL: //localhost:5000/todoapp-//api/login METHOD: POST Body: { "email":"Add email that is assigned for user in console", "password": "Add password that is assigned for user in console" }

postmanの送信リクエストボタンを押すと、次の出力が得られます。

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

今後のパートでこのトークンを使用して、ユーザーの詳細取得します。このトークンは60分で期限切れになることに注意してください。新しいトークンを生成するには、このAPIを再度使用します。

2.ユーザーサインアップAPI:

Firebaseのデフォルトの認証メカニズムでは、メールやパスワードなどの情報のみを保存できます。ただし、このユーザーがそのToDoを所有しているかどうかを識別して、読み取り、更新、削除の操作を実行できるようにするための詳細情報が必要です。

この目標を達成するために、usersという新しいコレクションを作成します。このコレクションの下に、ユーザー名に基づいてToDoにマップされるユーザーのデータを保存します。各ユーザー名は、プラットフォーム上のすべてのユーザーに対して一意になります。

に移動しますindex.js。signUpUserメソッドをインポートし、それにPOSTルートを割り当てます。

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

次に、に移動してvalidators.jsvalidateLoginDataメソッドの下に次のコードを追加します。

// validators.js const isEmail = (email) => { const emailRegEx = /^(([^()\[\]\\.,;:\[email protected]"]+(\.[^()\[\]\\.,;:\[email protected]"]+)*)|(".+"))@((\[[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 }; };

次に、に移動してusers.jsloginUserモジュールの下に次のコードを追加します。

// users.js exports.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' }); } }); }

ユーザーデータを検証し、その後、FirebasecreateUserWithEmailAndPasswordモジュールにメールとパスワードを送信してユーザーを作成します。ユーザーが正常に作成されたら、ユーザーの資格情報をデータベースに保存します。

これで、SignUpAPIが完成しました。firebase serveコマンドを実行して、郵便配達員に移動します。新しいリクエストを作成し、メソッドタイプをPOSTとして選択します。URLと本文を追加します。

URL: //localhost:5000/todoapp-//api/signup METHOD: POST Body: { "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" }

postmanの送信リクエストボタンを押すと、次の出力が得られます。

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

次に、Firebaseコンソール> [データベース]に移動すると、次の出力が表示されます。

ご覧のとおり、ユーザーのコレクションは1つのドキュメントで正常に作成されています。

3.ユーザープロファイル画像をアップロードします。

ユーザーは自分のプロフィール写真をアップロードできるようになります。これを実現するために、ストレージバケットを使用します。行くFirebaseコンソール>ストレージとをクリックして取得開始ボタンを押します。構成については、以下のGIFに従ってください。

次に、[ストレージ]の下の[ルール]タブに移動し、下の画像のようにバケットアクセスの権限を更新します。

プロフィール写真をアップロードするには、という名前のパッケージを使用しますbusboy。このパッケージをインストールするには、次のコマンドを使用します。

npm i busboy

に移動しindex.jsます。既存のsignUpUserメソッドの下にuploadProfilePhotoメソッドをインポートします。また、POSTルートをそのメソッドに割り当てます。

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

ここでは、そのアカウントに関連付けられたユーザーのみが画像をアップロードできるように、認証レイヤーを追加しました。次にauth.jsfunctions> utilsディレクトリで名前が付けられたファイルを作成します。そのファイルに次のコードをコピーして貼り付けます。

// auth.js const { 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); }); };

ここでは、firebaseverifyIdTokenモジュールを使用してトークンを検証しています。その後、ユーザーの詳細をデコードし、既存のリクエストに渡します。

に移動し、メソッドのusers.js下に次のコードを追加しますsignup

// users.js deleteImage = (imageName) => { const bucket = admin.storage().bucket(); const path = `${imageName}` return bucket.file(path).delete() .then(() => { return }) .catch((error) => { return }) } // Upload profile picture exports.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 = `//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); };

これで、Upload Profile PictureAPIが完成しました。firebase serveコマンドを実行して、郵便配達員に移動します。新しいリクエストを作成し、メソッドタイプをPOSTとして選択し、URLを追加して、本文セクションでタイプをform-dataとして選択します。

リクエストは保護されているため、ベアラトークンも送信する必要があります。ベアラトークンを送信するには、トークンの有効期限が切れている場合は再度ログインします。その後、Postman App> Authorizationタブ> Type> Bearer Tokenで、トークンセクションにトークンを貼り付けます。

URL: //localhost:5000/todoapp-//api/user/image METHOD: GET Body: { REFER THE IMAGE down below }

postmanの送信リクエストボタンを押すと、次の出力が得られます。

{ "message": "Image uploaded successfully" }

4.ユーザーの詳細を取得します。

ここでは、データベースからユーザーのデータを取得しています。に移動してindex.jsgetUserDetailメソッドをインポートし、それにGETルートを割り当てます。

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

次に、に移動してusers.jsuploadProfilePhotoモジュールの後に次のコードを追加します。

// users.js exports.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 }); }); }

firebase doc()。get()モジュールを使用して、ユーザーの詳細を取得しています。これで、GET User DetailsAPIが完成しました。firebase serveコマンドを実行して、郵便配達員に移動します。新しいリクエストを作成し、メソッドタイプGETを選択して、URLと本文を追加します。

リクエストは保護されているため、ベアラトークンも送信する必要があります。ベアラトークンを送信するには、トークンの有効期限が切れている場合は再度ログインします。

URL: //localhost:5000/todoapp-//api/user METHOD: GET

postmanの送信リクエストボタンを押すと、次の出力が得られます。

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

5.ユーザーの詳細を更新します。

次に、ユーザーの詳細を更新する機能を追加しましょう。に移動し、index.js次のコードをコピーして貼り付けます。

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

次に、に移動して、既存のモジュールの下にモジュールusers.jsを追加しupdateUserDetailsますgetUserDetails

// users.js exports.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" }); }); }

ここでは、firebase使用しているアップデート方法を。これで、Update User DetailsAPIが完成しました。上記のGetUser Details APIの場合と同じ手順を変更して、リクエストを実行します。ここでリクエストに本文を追加し、POSTとしてメソッドを追加します。

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

postmanの送信リクエストボタンを押すと、次の出力が得られます。

{ "message": "Updated successfully" }

6. Todo APIの保護:

選択したユーザーのみがアクセスできるようにTodoAPIを保護するために、既存のコードにいくつかの変更を加えます。まず、index.js次のように更新します。

// index.js // Todos app.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);

すべてのAPI呼び出しにトークンが必要であり、特定のユーザーのみがアクセスできるように、すべてのTodoルートを追加して更新しましたauth

その後todos.jsfunctions> APIsディレクトリの下に移動します

  1. Todo APIの作成:を開き、postOneTodoメソッドのtodos.js下に次のようにユーザー名キーを追加します。
const newTodoItem = { .., username: request.user.username, .. }

2. GET All Todos API:を開きtodos.jsgetAllTodosメソッドの下に次のようにwhere句を追加します。

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

Firebase Serveを実行し、GETAPIをテストします。ベアラトークンを送信することを忘れないでください。ここでは、次のような応答エラーが発生します。

{ "error": 9 }

コマンドラインに移動すると、次の行がログに記録されます。

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

これを開く ブラウザで、[インデックスの作成]をクリックします。

インデックスが作成されたら、リクエストを再度送信すると、次の出力が得られます。

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

3.   Todo APIの削除:を開きtodos.jsdeleteTodoメソッドの下に次の条件を追加します。この条件を、!doc.exists条件の下のdocument.get()。then()クエリ内に追加します。

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

これまでのディレクトリ構造:

+-- 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

これで、APIバックエンドが完成しました。休憩してコーヒーを飲み、その後、アプリケーションのフロントエンドの構築を開始します

セクション3:ユーザーダッシュボード

これでセクション次のコンポーネントを開発します。

  1. ReactJSとマテリアルUIを構成します。
  2. ログインおよびサインアップフォームの作成。
  3. アカウントセクションの構築。

このセクションで実装されているユーザーダッシュボードコードは、このコミットで見つけることができます。

1.ReactJSとマテリアルUIを構成します。

create-react-appテンプレートを使用します。これにより、アプリケーションを開発するための基本的な構造が得られます。これをインストールするには、次のコマンドを使用します。

npm install -g create-react-app

関数ディレクトリが存在するプロジェクトのルートフォルダに移動します。次のコマンドを使用して、フロントエンドアプリケーションを初期化します。

create-react-app view

バージョン使用することを忘れないでくださいv16.13.1のをReactJSライブラリ

インストールが完了すると、コマンドラインログに次のように表示されます。

cd view npm start Happy hacking!

これで、Reactアプリケーションを構成しました。次のディレクトリ構造が得られます。

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

次に、コマンドを使用してアプリケーションを実行しますnpm start。のブラウザに移動する//localhost:3000/と、次の出力が表示されます。

次に、不要なコンポーネントをすべて削除します。ビューディレクトリに移動し、すべてのファイルを削除しますこれは持って[削除]を彼らの前に。これについては、以下のディレクトリツリー構造を参照してください。

+-- 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

index.htmlパブリックディレクトリの下に移動し、次の行を削除します。

次にApp.js、srcディレクトリの下に移動し、古いコードを次のコードに置き換えます。

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

に移動してindex.js、次のインポートを削除します。

import './index.css'

私は削除してApp.cssおらず、このアプリケーションで使用していません。ただし、削除または使用する場合は、自由に行うことができます。

上のブラウザに移動する//localhost:3000/と、空白の画面出力が表示されます。

Material UIをインストールするには、ビューディレクトリに移動し、次のコマンドをターミナルにコピーして貼り付けます。

npm install @material-ui/core

MaterialUIライブラリのバージョンv4.9.8を使用することを忘れないでください。

2.ログインフォーム:

ログインフォームを作成するには、に移動しApp.jsます。上部にApp.js次のインポートを追加します。

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

TodoAppのルートを割り当てるためにSwitchRouteを使用しています。ここでは、/ loginルートのみを追加し、それにログインコンポーネントを割り当てます。

// App.js 

作成したページの既存のディレクトリの下に表示するディレクトリと名前のファイルlogin.js下のページのディレクトリを。

MaterialUIコンポーネントとAxiosパッケージを次の場所にインポートしますlogin.js

// login.js // Material UI components import 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';

ログインページに次のスタイルを追加します。

// login.js const 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' } });

フォームと送信ハンドラーを含むloginという名前のクラスを作成します。

// login.js class 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      Sign In {loading && }     {"Don't have an account? Sign Up"}    {errors.general && (  {errors.general}  )} ); } }

このファイルの最後に、次のエクスポートを追加します。

export default withStyles(styles)(login); 

次のように、firebase関数のURLを追加して> package.json表示します。

注意:既存のbrowserslistJSONオブジェクトの下にproxyという名前のキーを追加してください
"proxy": "//-todoapp-.cloudfunctions.net/api"

次のコマンドを使用して、Axiosおよびマテリアルアイコンパッケージをインストールします。

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

にログインルートを追加しましたApp.js。でlogin.js、状態を処理し、Axiosパッケージを使用してログインAPIにPOSTリクエストを送信するクラスコンポーネントを作成しました。リクエストが成功した場合、トークンを保存します。応答でエラーが発生した場合は、UIにレンダリングするだけです。

のブラウザに移動する//localhost:3000/loginと、次のログインUIが表示されます。

間違った資格情報を入力するか、空のリクエストを送信してみてください。エラーが発生します。有効なリクエストを送信してください。開発者コンソール>アプリケーションに移動します。ユーザートークンがローカルストレージに保存されていることがわかります。ログインが成功すると、ホームページに戻ります。

3.サインアップフォーム:

サインアップフォームを作成するにはApp.js、既存のRouteコンポーネントに移動して、以下の行で更新します。

// App.js 

インポートすることを忘れないでください:

// App.js import signup from './pages/signup';

pagesディレクトリのsignup.js下に名前の付いたファイルを作成します。

signup.js内で、MaterialUIとAxiosパッケージをインポートします。

// signup.js import 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';

サインアップページに次のスタイルを追加します。

// signup.js const 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' } }); 

フォームと送信ハンドラーを含むsignupという名前のクラスを作成します。

// signup.js class 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                              Sign Up {loading && }     Already have an account? Sign in ); } }

このファイルの最後に、次のエクスポートを追加します。

export default withStyles(styles)(signup); 

サインアップコンポーネントのロジックは、ログインコンポーネントと同じです。のブラウザに移動する//localhost:3000/signupと、次のサインアップUIが表示されます。サインアップが成功すると、ホームページに戻ります。

間違った資格情報を入力するか、空のリクエストを送信してみてください。エラーが発生します。有効なリクエストを送信してください。開発者コンソール>アプリケーションに移動します。ユーザートークンがローカルストレージに保存されていることがわかります。

4.アカウントセクション:

アカウントページを作成するには、最初にアカウントセクションをロードするホームページを作成する必要があります。に移動してApp.js、次のルートを更新します。

// App.js 

インポートを忘れないでください:

// App.js import home from './pages/home';

home.js。という名前の新しいファイルを作成します。このファイルは、アプリケーションのインデックスになります。アカウントセクションとTodoセクションはどちらも、ボタンのクリックに基づいてこのページに読み込まれます。

Material UIパッケージ、Axiosパッケージ、カスタムアカウント、todoコンポーネント、および認証ミドルウェアをインポートします。

// home.js import 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'

次のようにdrawerWidthを設定します。

const drawerWidth = 240;

ホームページに次のスタイルを追加します。

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

homeという名前のクラスを作成します。このクラスには、ユーザーのプロフィール写真、名、姓を取得するためのAPI呼び出しがあります。また、表示するコンポーネント(Todoまたはアカウント)を選択するロジックもあります。

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

ここのコードでは、それauthMiddleWare(this.props.history);が使用されていることがわかります。このミドルウェアは、authTokenがnullかどうかをチェックします。はいの場合、ユーザーをに押し戻しlogin.jsます。これは、ユーザーが/サインアップまたはログインせずにルートにアクセスできないようにするために追加されています。このファイルの最後に、次のエクスポートを追加します。

export default withStyles(styles)(home); 

今、あなたはこのコードが何をしているのか疑問に思ってhome.jsいますか?

 {this.state.render ?  : } 

ボタンクリックで設定しているレンダリング状態をチェックしています。コンポーネントディレクトリを作成し、そのディレクトリの下に2つのファイルを作成しましょう:account.jstodo.js

utilという名前のディレクトリとauth.jsそのディレクトリの下にという名前のファイルを作成しましょう。次のコードをコピーして貼り付けますauth.js

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

とりあえず中 todo.jsファイルHelloI amtodoというテキストをレンダリングするクラスを作成します。次のセクションでは、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));

それでは、アカウントセクションの時間です。Material UI、clsx、axios、およびauthmiddleWareユーティリティをにインポートしますaccount.js

// account.js import 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';

アカウントページに次のスタイルを追加します。

// account.js const 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' } });

accountという名前のクラスコンポーネントを作成します。とりあえず、次のコードをコピーして貼り付けてください。

// account.js class 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}  

このファイルの最後に、次のエクスポートを追加します。

export default withStyles(styles)(account); 

account.js使用されるコンポーネントがたくさんあります。まず、アプリケーションがどのように見えるかを見てみましょう。その後、使用されるすべてのコンポーネントとそれらが使用される理由について説明します。

ブラウザに移動すると、トークンの有効期限が切れると、loginページにリダイレクトされます  。詳細を追加して、再度ログインしてください。それが完了したら、[アカウント]タブに移動すると、次のUIが表示されます。

アカウントセクションには3つのハンドラーがあります。

  1. componentWillMount:これはReactに組み込まれているライフサイクルメソッドです。これを使用して、レンダリングライフサイクルの前にデータをロードし、状態値を更新しています。
  2. ProfilePictureUpdate:これは、ユーザーが[写真のアップロード]ボタンをクリックしたときにデータをサーバーに送信し、ページを再読み込みしてユーザーの新しいプロフィール写真を表示するために使用しているカスタムハンドラーです。
  3. updateFormValues:これは、ユーザーの詳細を更新するためのカスタムハンドラーでもあります。ここで、ユーザーは自分の名、姓、国を更新できます。バックエンドロジックはこれらのキーに依存しているため、メールとユーザー名の更新は許可されていません。

これらの3つのハンドラーを除いて、それはその上にスタイリングを備えたフォームページです。ビューフォルダ内のこれまでのディレクトリ構造は次のとおりです。

+-- 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

これで、アカウントダッシュボードが完成しました。コーヒーを飲みに行って休憩し、次のセクションでTodoダッシュボードを作成します。

セクション4:Todoダッシュボード

これでセクションは、Todosダッシュボードのこれらの機能のUIを開発します。

  1. Todoを追加します。
  2. すべてのやることを取得します。
  3. ToDoを削除する
  4. ToDoを編集する
  5. ToDoを取得する
  6. テーマの適用

このセクションで実装されているTodoダッシュボードコードは、このコミットで見つけることができます。

コンポーネントディレクトリの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';

また、既存のスタイルコンポーネントに次の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] } });

ポップアップダイアログボックスのトランジションを追加します。

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

既存の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.buttonType === 'Edit' ? 'Save' : 'Submit'}                {this.state.todos.map((todo) => (     {todo.title}   {dayjs(todo.createdAt).fromNow()}   {`${todo.body.substring(0, 65)}`}     this.handleViewOpen({ todo })}> {' '} View{' '}   this.handleEditClickOpen({ todo })}> Edit   this.deleteTodoHandler({ todo })}> Delete     ))}    {this.state.title}       ); } } }

このファイルの最後に、次のエクスポートを追加します。

export default withStyles(styles)(todo); 

最初にUIがどのように機能するかを理解し、その後コードを理解します。ブラウザに移動すると、次のUIが表示されます。

右下の[追加]ボタンをクリックすると、次の画面が表示されます。

Todoのタイトルと詳細を追加し、送信ボタンを押します。次の画面が表示されます。

この後、表示ボタンをクリックすると、Todoの詳細をすべて表示できます。

[編集]ボタンをクリックすると、ToDoを編集できるようになります。

削除ボタンをクリックすると、Todoを削除できます。ダッシュボードがどのように機能するかを理解したので、ダッシュボードで使用されるコンポーネントを理解します。

1. Todoの追加:Todoの追加を実装するには、マテリアル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 }); };

これ以外に、[Todoの追加]ボタンの配置も変更します。

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

次に、リストタグをこのダイアログ内のフォームに置き換えます。新しい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   

ザ・handleSubmitbuttonType状態を読み取るロジックで構成されています。状態が空の文字列の(“”)場合、Add TodoAPIに投稿されます。状態がEditthenの場合、そのシナリオではEditTodoが更新されます。

2. Todo取得する:使用するTodoを表示するために、Grid containerその中にを配置しGrid itemます。その中で、Cardコンポーネントを使用してデータを表示します。

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

APIがToDoアイテムをリストで送信するときに、マップを使用してToDoアイテムを表示します。componentWillMountライフサイクルを使用して、レンダリングが実行される前の状態を取得および設定します。3つのボタン(表示、編集、削除)があるため、ボタンがクリックされたときの操作を処理するには3つのハンドラーが必要です。これらのボタンについては、それぞれのサブセクションで学習します。

3. Todoの編集:Todoの編集では、Todoの追加で使用されるダイアログポップアップコードを再利用しています。ボタンのクリックを区別するために、buttonType状態を使用しています。Add Todoの場合、  buttonType状態は(“”)edittodoの場合はEditです。

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

このhandleSubmitメソッドでは、buttonType状態を読み取り、それに応じてリクエストを送信します。

4. Todoの削除:このボタンをクリックすると、TodoオブジェクトがdeleteTodoHandlerに送信され、さらにバックエンドにリクエストが送信されます。

 this.deleteTodoHandler({ todo })}>Delete

5. Todoの表示:データを表示するときに、ユーザーがTodoの内容を垣間見ることができるようにデータを切り捨てました。しかし、ユーザーがそれについてもっと知りたい場合は、表示ボタンをクリックする必要があります。

このために、カスタマイズされたダイアログを使用します。その中で、DialogTitleとDialogContentを使用します。タイトルとコンテンツが表示されます。DialougeContentでは、フォームを使用して、ユーザーが投稿したコンテンツを表示します。(これは私が見つけた解決策の1つであり、他の解決策を自由に試すことができます。)

// This is used to remove the underline of the Form InputProps={{ disableUnderline: true }} // This is used so that user cannot edit the data readonly

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

のボタンにテーマを適用できませんでしtodo.jsCardActions。表示、編集、削除ボタンのカラータグを追加します。

ブラウザに移動すると、アプリの色が異なることを除いて、すべてが同じであることがわかります。

これで完了です。ReactJSとFirebaseを使用してTodoAppを構築しました。この時点までにそれを構築した場合は、この成果について非常に大きなおめでとうございます。

TwitterとGithubで気軽に連絡してください。