【Next.js】StripeとFirebaseにユーザー&クレカ登録同期処理ハンズオン

f:id:nekorokkekun:20191010233213p:plain:w1000
Next.jsで決済処理を実装したいという場合、FirebaseとStripeへ同時にユーザー登録をしたいという場合があるはずです。(厳密には、Stripeは顧客登録)

今回はStripeとFirebase Authenticationへのユーザー登録を同期させ、その後、クレジットカードを登録するという仕組みが体験できるハンズオンを進めていきます。

前提として

以下の項目については詳しく説明しませんので、理解しているという前提で進めていきます。

  • Firebaseの設定
  • Firebase Authentication
  • HOC(高階コンポーネント)
  • Stripeへのアカウント登録

また、以下の記事を参考にしていただければ、上記の前提知識についても身に付けられるかと思います。

nekorokkekun.hatenablog.com
nekorokkekun.hatenablog.com

全体の仕組み

複雑ですが、以下の仕組みで今回の実装を進めていきます。
f:id:nekorokkekun:20191010212532p:plain

ベースプロジェクトの準備

まずはベースとなるNext.jsのプロジェクトをgit cloneしましょう。

$ git clone git@github.com:mesh1nek0x0/stripe-nextjs-on-firebase.git

次にプロジェクト内に移動し、今回使用するブランチへ移動します。

$ cd stripe-nextjs-on-firebase/
$ git checkout start

Firebaseの準備

次にFirebaseのプロジェクトを作成し、Firebase SDK snippetをプロジェクトの設定から取得しましょう。

(例)Firebase SDK snippet

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/7.1.0/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/7.1.0/firebase-analytics.js"></script>

<script>
  // Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    authDomain: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    databaseURL: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    projectId: "practice-pf-funclub",
    storageBucket: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    appId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    measurementId: "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  firebase.analytics();
</script>


次に.firebasercの中身を自身のFirebaseプロジェクト名に書き換えましょう。

{
  "projects": {
    "default": "practice-pf-funclub"
  }
}

.envをルートディレクトリに作成し、Firebase SDK snippetから取得したデータを以下のように書き換えましょう。

.env

apiKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
authDomain=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
databaseURL=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
projectId=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
storageBucket=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
messagingSenderId=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
appId=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
// stripeのパブリッシュキー(pkから始まるもの)も同時に設定
stripeKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxx

次にlib/firebase/index.jsの設定に追記し、firestoreを使用できるようにしておきます。
lib/firebase/index.js

import firebase from "firebase/app";
import "firebase/auth";
import "firebase/firestore";

const config = {
  apiKey: process.env.apiKey,
  authDomain: process.env.authDomain,
  databaseURL: process.env.databaseURL,
  projectId: process.env.projectId,
  storageBucket: process.env.storageBucket,
  messagingSenderId: process.env.messagingSenderId
};
if (!firebase.apps.length) {
  firebase.initializeApp(config);
}
const auth = firebase.auth();
const firestore = firebase.firestore();
export { auth, firestore, firebase };

Firebaseの料金プランを変更

実はFirebaseの料金プランがFreeのままの場合、Stripeのような外部へ通信することができません。

そのため、Firebaseのコンソールから料金プランを変更しておきましょう。

Blaze(従量課金制)だと、一定以下の通信は無料となるので、試しに使ってみる分にはこちらがおすすめです。

Firebase AuthenticationでGoogle登録を有効にする

Firebase Authenticationを使用してSNSユーザー登録を実装していきます。

そのため、FirebaseコンソールからFirebase AuthenticationのSNSユーザー認証におけるGoogleを有効にしておきましょう。

詳しくは以下の記事に記載されています。
nekorokkekun.hatenablog.com

StripeとFirebase Authenticationのユーザー登録同期

以下の順に処理を行っていきます。

  • Next.js上でサインイン
  • Firebase Authenticationでユーザーが作成される
  • Firebase functionsのトリガーが起動
  • Cloud Firestoreのstripe_customersコレクション以下にユーザーIDが保存される
  • Stripeの顧客に登録される

上記の流れを図にすると以下のようになります。
f:id:nekorokkekun:20191010212700p:plain

工程は複雑ですが、実際はユーザー登録のみで一気に全て行えるように設定していきましょう。

現状の確認

まずは現在のNext.jsの画面を確認してみましょう。

$ yarn install
$ yarn dev

これで http://localhost:3000 にアクセスしてみましょう。

すると以下のような画面が表示されます。
f:id:nekorokkekun:20191010212745p:plain

実際に使用するのは、「Sign in using google」の部分となります。

Firebase functions内の設定

まずはFirebase functionsの設定をしていきましょう。

Stripeモジュールの追加

functionsディレクトリ内でStripeモジュールを追加します。

$ cd functions/
$ yarn add stripe 
Firebase functionsにStripeのSecret API Keyを設定

次にFirebase functionsの環境変数にStripeのSecret API Keyを設定します。

firebase functions:config:set stripe.token="sk_test_xxxxxxxxxxxxxxxxxxxxxx"
✔  Functions config updated.

Please deploy your functions for the change to take effect by running firebase deploy --only functions
Firebase functions本体の設定

次にFirebase functions本体を設定しましょう。
functions/index.js

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const stripe = require("stripe")(functions.config().stripe.token);
const currency = functions.config().stripe.currency || "JPY";

exports.createStripeCustomer = functions.auth.user().onCreate(async user => {
  const customer = await stripe.customers.create({ email: user.email });
  return admin
    .firestore()
    .collection("stripe_customers")
    .doc(user.uid)
    .set({ customer_id: customer.id });
});

こちらは細かく解説をしていきます。

const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const stripe = require("stripe")(functions.config().stripe.token);
const currency = functions.config().stripe.currency || "JPY";

この辺りは必要なモジュールやライブラリなどを読んでいます。

functions.auth.user().onCreate

「Firebase Authenticationでのユーザーをトリガーに以下の処理を行う」という意味になります。

const customer = await stripe.customers.create({ email: user.email });

「Stripeの顧客作成を行う上でStripeのユーザーのemail情報にはFirebase Authenticationのユーザーのemailを設定する」という意味になります。

こちらで出てくるawaitの後の「stripe」は、先ほどaddしたstripeモジュールから呼び込んでいるものです。

  return admin
    .firestore()
    .collection("stripe_customers")
    .doc(user.uid)
    .set({ customer_id: customer.id });

「Firestoreのstripe_customersコレクション > user.uidドキュメント以下にcustomer_idフィールドを作成して値を格納する」という意味になります。

このFirebase functionsを設定することで、ユーザー登録をすることによって一気にStripeとFirebase Authenticationにユーザー登録をし、Firestoreにcustomer_idを格納するというところまでの処理が進みます。

Firebase functionsのデプロイ

functions/index.jsの設定をFirebase functionsにデプロイ するために、functionsディレクトリ内で以下のコマンドを入力しましょう。

$ yarn deploy

これで「✔ functions[createStripeCustomer(us-central1)]: Successful update operation. 」と表示されればデプロイ 完了です。

StripeとFirebase Authenticationの同期確認

Next.jsのルートディレクトリに戻り、yarn devで再びlocalhost3000へアクセスしましょう。

ページに「Sign In using google」が表示されているはずなので、処理を進めてみましょう。

サインインが成功した旨のメッセージがブラウザ上のモーダルウィンドウで確認できたら、Firebase AuthenticationとStripeのそれぞれのコンソール上で登録ができているかどうかの確認をしてみてください。(Stripeは顧客登録の反映に若干のタイムラグがあります)

StripeのtokenをFirestoreに格納する

Stripeは、顧客のクレジットカード情報を私たちの代わりに保管してくれます。
そして登録されたクレジットカード情報を操作するためにはtokenを発行しなければなりません。

tokenの説明についてわかりやすい解説があったため引用します。

Stripeによるクレジットカード決済では何よりも”トークン”を取得する事が重要となります。

サービス提供者側では直接クレジットカードの情報を取得・管理する必要はなく、Stripe側でそれらを全て管理してくれます。
その際、Stripeが取得したクレジットカード情報を操作する為に実用となるのがこの”トークン”となります。


単発の決済であれば決済する際にクレジットカード情報をStrip経由で取得し、その取得した”トークン”に対して課金処理を行う事で実現します。
一方で定期的な課金を行う場合、取得した”トークン”情報を”顧客(Customer)”に結びつけ、その”顧客(Customer)”に対して課金処理を行う事で実現する事ができます。

このように、Stripeでクレジットカード決済を実装する場合、何よりもこの”トークン”の取得と管理について理解する必要があります。
引用元:
https://erorr.org/104/#i

以下のような流れでこちらの処理を実現します。

  • Next.jsでクレジットカード情報を入力
  • Stripeからtokenを発行
  • Firestoreへtokenを格納

上記の流れを図にすると以下のようになります。
f:id:nekorokkekun:20191010221126p:plain

クレジットカード登録ページの作成

まずはクレジットカードを登録するフォームがあるページを作成しましょう。

最初にReact上でStripeを使用する際の依存パッケージのインストールを行います。

$ yarn add react-stripe-elements

components/CheckoutForm.js

import React, { Component } from "react";
import { CardElement, injectStripe } from "react-stripe-elements";

class CheckoutForm extends Component {
  constructor(props) {
    super(props);
    this.submit = this.submit.bind(this);
  }

  async submit(ev) {
    const { token, error } = await this.props.stripe.createToken();
    this._element.clear();
    console.log(token);
  }

  render() {
    return (
      <div className="checkout">
        <p>Would you like to complete the purchase?</p>
        <CardElement onReady={c => (this._element = c)} />
        <button onClick={this.submit}>Purchase</button>
      </div>
    );
  }
}

export default injectStripe(CheckoutForm);

こちらは、Stripeのクレジットカード入力フォームコンポーネントです。

exportの際に「injectStripe」というHOCの引数にCheckoutFormコンポーネントを入れています。

このinjectStripeは公式によると…

injectStripe高次コンポーネント(HOC)を使用して、エレメントツリーに支払いフォームコンポーネントを構築します。

injectStripe HOCは、Elementsグループを管理するthis.props.stripeプロパティを提供します。

挿入されたコンポーネント内で、次のいずれかを呼び出すことができます。

  • this.props.stripe.createPaymentMethod
  • this.props.stripe.createToken
  • this.props.stripe.createSource
  • this.props.stripe.handleCardPayment
  • this.props.stripe.handleCardSetup

参考:
GitHub - stripe/react-stripe-elements: React components for Stripe.js and Stripe Elements

フォームに入力したクレジットカードのデータをpropsに格納し、指定された5つの情報をpropsから取り出すことができるとのことです。

そして以下の通り、injectStripeによってcreateTokenの情報をpropsから取り出していますね。

  async submit(ev) {
    const { token, error } = await this.props.stripe.createToken();
    this._element.clear();
    console.log(token);
  }


pages/purchase.js

import React, { Component } from "react";
import Head from "next/head";
import { Elements, StripeProvider } from "react-stripe-elements";
import CheckoutForm from "../components/CheckoutForm";

export default class Purchase extends Component {
  constructor(props) {
    super(props);
    this.state = { stripe: null };
  }
  componentDidMount() {
    // Create Stripe instance in componentDidMount
    // (componentDidMount only fires in browser/DOM environment)
    this.setState({
      stripe: window.Stripe(process.env.stripeKey)
    });
  }

  render() {
    return (
      <StripeProvider stripe={this.state.stripe}>
        <div className="example">
          <Head>
            <script src="https://js.stripe.com/v3/" />
          </Head>
          <h1>React Stripe Elements Example</h1>
          <Elements>
            <CheckoutForm />
          </Elements>
        </div>
      </StripeProvider>
    );
  }
}
  componentDidMount() {
    // Create Stripe instance in componentDidMount
    // (componentDidMount only fires in browser/DOM environment)
    this.setState({
      stripe: window.Stripe(process.env.stripeKey)
    });
  }

stateのstripeにdotenvモジュールで環境変数のstripeKeyを呼び出し、設定しています。

現状の確認

ここまでできたら、
http://localhost:3000/purchase
にアクセスしてみましょう。

そうすると、クレジットカード番号・使用期限・CVCの入力フォームのあるページが表示されます。

tokenをユーザに紐付けて保存

現在、tokenはユーザに紐付いていません。

しかし、実際にはサインイン済みのユーザがクレジットカードの登録を行うため、サインインしたユーザとtokenが紐付いている状態が適切です。

そこでwithAuth(サインインユーザのみアクセスを認証するHOC)のstateに、サインインしたユーザの情報を保有させ、tokenを作成した際にユーザと紐付けを行いましょう。

ちなみにwithAuthというHOCについての解説は以下の記事に詳しく書かれています。
nekorokkekun.hatenablog.com


具体的には以下のようなコードになります。

components/helpers/withAuth.js

import React from "react";
import router from "next/router";
import { auth } from "../../lib/firebase";
const withAuth = Component => {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        status: "LOADING",
        user: {}
      };
    }
    componentDidMount() {
      auth.onAuthStateChanged(authUser => {
        if (authUser) {
          this.setState( {
            status: "SIGNED_IN",
            user: authUser
          });
        } else {
          router.push("/");
        }
      });
    }
    renderContent() {
      const { status, user } = this.state;
      if (status == "LOADING") {
        return <h1>Loading ......</h1>;
      } else if (status == "SIGNED_IN") {
        return <Component {...this.props} currentUser={user} />;
      }
    }
    render() {
      return <>{this.renderContent()}</>;
    }
  };
};
export default withAuth;

こちらも重要なポイントの解説を行います。

        if (authUser) {
          this.setState( {
            status: "SIGNED_IN",
            user: authUser
          });

こちらで、認証したユーザのみ、認証ユーザの情報をstateへセットしています。

return <Component {...this.props} currentUser={user} />;

stateにセットした認証ユーザの情報をpropsに渡し、別のコンポーネントで使用できるようにしましょう。


CheckoutForm.jsにwithAuth.jsを追記します。

import React, { Component } from "react";
import { CardElement, injectStripe } from "react-stripe-elements";
import withAuth from "../components/helpers/withAuth";
import { firestore } from "../lib/firebase";

class CheckoutForm extends Component {
  constructor(props) {
    super(props);
    this.submit = this.submit.bind(this);
  }

  async submit(ev) {
    const { token, error } = await this.props.stripe.createToken();
    firestore
      .collection("stripe_customers")
      .doc(this.props.currentUser.uid)
      .collection("tokens")
      .add({ token: token.id })
      .then(() => {
        this._element.clear();
      });
  }

  render() {
    return (
      <div className="checkout">
        <p>Would you like to complete the purchase?</p>
        <CardElement onReady={c => (this._element = c)} />
        <button onClick={this.submit}>Purchase</button>
      </div>
    );
  }
}

export default withAuth(injectStripe(CheckoutForm));

tokenがサインインユーザに紐付いているか確認

http://localhost:3000 へアクセスし、クレジットカード情報を入力してサブミットしてみましょう。

ダミーデータとして

カード番号: 4242424242424242
期限:242
CVC:なんでも
郵便番号:なんでも

でテストができます。

サブミット後、Firestoreのstripe_customers > ユーザーID > tokens の中にランダムな英数字が入っていれば成功です。

token作成をトリガーにしたStripe sourceの作成

Stripeでtokenを作成した際にsourceというものを同時に作成します。

このsourceについては以下のサイトで詳しく解説がなされているため、引用します。

sourceは買い手が決済するために使う「モノ」を抽象化した概念であると言えます。その具象としては、クレジットカードやACH(銀行振込のようなもの)があります。1回きりの決済としてsourceに対して直接課金することもできますし、買い手に対して複数回課金するために買い手にsourceを関連付けておくこともできます。

引用元:
Stripe Sources APIにおける決済の抽象化 - blog.kymmt.com

こちらでは以下の処理を実装していきます。

  • tokenをFIrestoreに格納
  • Firebase functionsがtoken作成をトリガーにしてStripeへsourceの作成依頼を送信
  • Stripeが依頼を受けてsourceを作成
  • 作成されたsourceがFirestoreに格納

上記を図にした場合、以下のようなものになります。
f:id:nekorokkekun:20191010232007p:plain

functionsに追記

上記の処理は全てFirebase functionsで行うため、実装自体もfucntions/index.js内に書き込みましょう。

functions/index.js

// すでに実装したものは省略

exports.addPaymentSource = functions.firestore
  .document("/stripe_customers/{userId}/tokens/{pushId}")
  .onCreate(async (snap, context) => {
    const source = snap.data();
    const token = source.token;
    if (source === null) {
      return null;
    }

    try {
      const snapshot = await admin
        .firestore()
        .collection("stripe_customers")
        .doc(context.params.userId)
        .get();
      const customer = snapshot.data().customer_id;
      const response = await stripe.customers.createSource(customer, {
        source: token
      });
      return admin
        .firestore()
        .collection("stripe_customers")
        .doc(context.params.userId)
        .collection("sources")
        .doc(response.fingerprint)
        .set(response, { merge: true });
    } catch (error) {
      await snap.ref.set({ error: userFacingMessage(error) }, { merge: true });
      console.error(error);
      console.log(`user: ${context.params.userId}`);
    }
  });

function userFacingMessage(error) {
  return error.type
    ? error.message
    : "An error occurred, developers have been alerted";
}

Firebase functionsにデプロイ

以下のコマンドでFirebase functionsにデプロイ しましょう。

$ cd functions
$ yarn deploy

以下のように表示されていればデプロイ 成功です。

✔  functions[createStripeCustomer(us-central1)]: Successful update operation. 
✔  functions[addPaymentSource(us-central1)]: Successful create operation. 

✔  Deploy complete!

現状の確認

一度、Firebase Authenticationのユーザを削除した後にもう一度ユーザ認証を行い、クレジットカード情報の登録をしてみましょう。

FIrestoreのstripe_customers > ユーザID > sourcesに英数字が登録されていれば成功です。

これでStripeとFirebaseにユーザー&クレカ登録同期処理ハンズオンは完了しました。お疲れ様でした!

【Next.js】Head内に要素を組み込む(外部スクリプトとか)

Next.jsでHead内にCSSや外部script、titleタグなどを組み込みたかったので調べてみました。


結論としては、

import Head from 'next/head'

をインポートし、

render関数内などに、

                <Head>
                    <script src="https://js.stripe.com/v2/"></script>
                </Head>

こんな感じでHeadタグを作って、組み込みたい要素を入れてあげるだけでOKです!

nextjs-docs-ja.netlify.com

【Firebase Functions】functions@: The engine "node" is incompatible with this module. Expected version "8". Got "10.16.3"エラーが吐き出される

Next.js内でFirebase Functionsを使用するためにyarn serveを行うと以下のようなエラーが。

error functions@: The engine "node" is incompatible with this module. Expected version "8". Got "10.16.3"
error Commands cannot run with an incompatible environment.

Firestoreが求めているnodeのバージョンが8なのですが、
実際に使用されているローカルのnodeのバージョンが10.16.3ですよ、というエラーでした。

nodeを8にダウングレードすると他のプロジェクトに影響が出るかもしれないということでanyenv、nodenvを入れてみたのですが正しく作動しなかったため以下のような対策をしました。

functions/package.json

  "engines": {
    "node": "8"
  },

上記、プロジェクト直下のfunctions/package.jsonを以下のように改変

  "engines": {
    "node": "10"
  },

これでnode10で動くようになりました。

【Laravel5.8】ログアウトのredirect先をカスタマイズしたい

Laravel5.8のmake:authで作成したログアウト機能で、redirect先をカスタマイズしたい際の方法です。


/magotaku/app/Http/Controllers/Auth/LoginController.php

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; // 追加

class LoginController extends Controller
{
    /*
    |--------------------------------------------------------------------------
    | Login Controller
    |--------------------------------------------------------------------------
    |
    | This controller handles authenticating users for the application and
    | redirecting them to your home screen. The controller uses a trait
    | to conveniently provide its functionality to your applications.
    |
    */

   // 書き換え
    use AuthenticatesUsers {
        logout as performLogout;
    }

    /**
     * Where to redirect users after login.
     *
     * @var string
     */
    protected $redirectTo = '/home';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('guest')->except('logout');
    }
    
   // logoutファンクションを作成
    public function logout(Request $request)
    {
        $this->performLogout($request);
  // redirect先は人それぞれカスタマイズ
        return redirect('/home');
    }
}

以上です。

参考
qiita.com

【Next.js】FirebaseのAuthenticationで登録したユーザー情報を描画する

f:id:nekorokkekun:20190925144437p:plain:w1000
前回の記事では、Next.jsにFirebaseのAuthenticationを使ってユーザー登録機能を追加しました。
nekorokkekun.hatenablog.com

そこで「登録したユーザーの情報を動的に描画するマイページ」を作りましょう。

前提

先ほどもご紹介した前回の記事でログイン機能とHOCの実装まではコピペでも構わないので済ませておいてください。
nekorokkekun.hatenablog.com

ユーザー情報の取得

とは言っても書き方はとても簡単です。

pages/dashboard.js

import React from 'react';
import withAuth from "../src/helpers/withAuth";
// firebaseをimport
import { firebase, auth } from "../src/firebase";

class Dashboard extends React.Component {
    render() {
  //  firebase.auth().currentUserでログインユーザーの情報が取得可能
        const user = firebase.auth().currentUser;
        return(
            <div>
            <h2>アカウント情報</h2>
            <h4>アカウント名</h4>
            {user.displayName}
            <h4>メールアドレス</h4>
            <p>
            {user.email} 
            </p>
            <img src={user.photoURL} />
            </div>
        )
    }

}
export default withAuth(Dashboard);

これで以下のような画面が表示されたはずです。
f:id:nekorokkekun:20190925143829p:plain

firebase.auth().currentUserの中身

firebase.auth().currentUserでログインユーザーの情報を取得しています。

中身はオブジェクト型になっており、先ほどのコードに登場した「displayName」や「email」「photoURL」なども全て登録したユーザーの情報から引き出されています。

他にも色々な情報が入っていますので、必要に応じて取得すると良いでしょう。

【Next.js】Firestoreから任意のデータを取得して描画する

f:id:nekorokkekun:20190925114408p:plain:w1000
今回はNext.jsでFirebaseから任意のデータを取得して描画する方法について解説をしていきます。

ウェブアプリを作成する際、レイアウトは決めておいてコンテンツの名前や説明文などはデータを取得してレイアウトにはめ込みたい、という場合がありますね。

このように動的なページを作成するための方法を以下で紹介します。

前提

Firebase Cloud Firestore保存したデータを取得することになります。

すでにFirebaseでプロジェクトが作成されており、Firestoreにデータがいくつか登録されているという前提で進みます。

まだFirebaseのプロジェクト作成やデータの登録ができていないという場合は以下の記事を読み、

  • Firebaseでプロジェクトの作成
  • データの登録
  • Next.jsプロジェクトの作成
  • FirebaseとNext.jsプロジェクトのDB接続

を済ませておいてください。

nekorokkekun.hatenablog.com
nekorokkekun.hatenablog.com

また上記ではわざわざデータ登録するためにUIを作成していますが、プロジェクトを作成後、サイドメニューからDatabaseを選ぶことでブラウザからコレクション・ドキュメントの登録(=データの登録)を行うことができます!

Firestoreの確認

まずはFirestoreにデータが保存できるかどうかの確認をしましょう。
ブラウザ上でFirebaseのコンソールからプロジェクト>サイドメニューのDatabaseで確認できます。

実際に見ると以下のようにデータが登録されていることがわかります。
f:id:nekorokkekun:20190925104133p:plain

FirestoreはNoSQLなのでMySQLなどのようにテーブル、カラム、レコードといったものはないのですが、イメージとしては

  • コレクション = テーブル
  • ドキュメント = レコード
  • データ = カラムと実際のデータ

と考えるとしっくり方も多いかもしれません。

Firestoreでは以下のようなイラストで図解を出してくれています。
f:id:nekorokkekun:20190925104420p:plain

データの一覧表示

まずは先程確認したデータを一覧表示できるページを作成しましょう。

pages/index.js

import { db } from '../lib/db';
import React from 'react';
import Link from 'next/link';

export default class Index extends React.Component {
  static async getInitialProps() {
    let result = await
      db.collection('fanPages')
      .get()
      .then(snapshot => {
        let data = []
        snapshot.forEach((doc) => {
          data.push(
            Object.assign({
              id: doc.id
            }, doc.data())
          )
        })
        return data
      }).catch(error => {
        return []
      })
    return {datas: result}
  }

  render() {
    const firestoreDatas = this.props.datas
    return (
        <div>
            <h3>Firestoreのデータ一覧</h3>
            <div>
              <ul>
              {firestoreDatas.map(fanPage =>
                <li key={fanPage.id}>
                    <Link href="/p/[detailid]" as={`/p/${fanPage.id}`}>
                      <a>{fanPage.artistName}</a>
                    </Link>
                </li>
              )}
              </ul>
            </div>
        </div>
        );
        }
    }

これで以下のような画面が表示されたのではないでしょうか。
f:id:nekorokkekun:20190925114157p:plain

長いのでいくつかに分けて解説をします。

getInitialPropsでFirestoreからデータの取得

  static async getInitialProps() {
    let result = await
      db.collection('fanPages')
      .get()
      .then(snapshot => {
        let data = []
        snapshot.forEach((doc) => {
          data.push(
            Object.assign({
              id: doc.id
            }, doc.data())
          )
        })
        return data
      }).catch(error => {
        return []
      })
    return {datas: result}
  }

getInitialPropsはクラスコンポーネントで使用できるメソッドです。
コンポーネント生成時にpropsを自動取得してくれるという便利なメソッドなので覚えておきたいですね。

肝心の中身も見ていきましょう。

Firestoreからデータ取得するコレクションを指定
  static async getInitialProps() {
    let result = await
      db.collection('fanPages')
      .get()
      .then(

まず「db.collection('fanPages')」でコレクション「fanPages」からデータ取得を行うことを宣言します。

「db」は変数で、Firestoreの接続設定を他ファイルから取得済みです。詳しくはこちらをご覧ください。

また上記のコードでは非同期処理も行なっています。

「async」と「await」が書かれている部分ですね。
重要なのは「await」を宣言する場所で、意味としては

「result以降の処理が終わるまで結果をreturnしないでね」

ということになります。

Firestoreからのデータ取得は時間が掛かりますので、awaitを配置しておかなければ、データ取得前に「result」がreturnされてしまうため、結果「空っぽのresult」しか返ってこないということになります。

Firestoreのデータを取得して配列に入れる
snapshot => {
        let data = []
        snapshot.forEach((doc) => {
          data.push(
            Object.assign({
              id: doc.id
            }, doc.data())
          )
        })
        return data
snapshotとは?
snapshot => {

こんな1行がありますが、「snapshot」とはFirestoreのその「瞬間」のデータが格納されているものです。
データ取得の際に写真を取るイメージで、その瞬間のデータをsnapshotと名付けているんですね。

データをドキュメントごとに配列に収める
          data.push(
            Object.assign({
              id: doc.id
            }, doc.data())

snapshotの1行上で「let data = []」と、空の配列を用意しました。
その後、Object.assignでdataの中にオブジェクト形式でFirestoreのデータをドキュメントごとに格納しています。

実際、この状態のdataをconsole.logで見てみると以下のように出力されています。

[ { id: 'JNTwv1d5W0g1yXQr667j',
    artistName: 'test1',
    body: 'test1',
    category: 'singer',
    monthlyFee: '1000',
    pageName: 'test1' },
  { id: 'aW749fTXcm99meV7x2WB',
    artistName: 'test2',
    body: 'test2',
    category: 'calligrapher',
    monthlyFee: '2000',
    pageName: 'test2' } ]

一番外側に来ているがdata = で定義した配列ですね。
そして、その中にドキュメントごとにオブジェクト型に整形されたFirestoreのデータが格納されていることがわかります。

ここで配列にわざわざ格納するのは、後ほどmap関数で展開するためです。

そして最後に「return data」で中身の入ったdataが「let result」で定義した「result」の中に入りました。

エラーの場合は空の配列を返す
      }).catch(error => {
        return []
      })

もしFirestoreからのデータ取得の際にエラーが発生した場合には、「return []」で空の配列をresultに格納する設定にしています。

resultを返してpropsとして扱えるようにする
return {datas: result}

先程までの処理でデータがdata配列に入り、その配列がresultという変数に格納されました。(エラーの場合は空のdata配列がresult変数に格納されています。)

そのresultをpropsである「datas」に渡します。(名前は任意です)
こうすることでコンポーネント内でpropsとして格納したFirebaseのデータを扱うことができるようになるというわけですね。

render関数で取得したデータを描画

  render() {
    const firestoreDatas = this.props.datas
    return (
        <div>
            <h3>Firestoreのデータ一覧</h3>
            <div>
              <ul>
              {firestoreDatas.map(fanPage =>
                <li key={fanPage.id}>
                    <Link href="/p/[detailid]" as={`/p/${fanPage.id}`}>
                      <a>{fanPage.artistName}</a>
                    </Link>
                </li>
              )}
              </ul>
            </div>
        </div>
        );
        }
    }

次にrender関数で取得したデータを描画しています。こちらも細かく分けてみていきますね。

propsを定数に格納
const datas = this.props.datas

先程getInitialPropsで返されたpropsは「this.props.datas」という呼び方で参照する(props値を使用する)ことができます。

しかし名称として長いため、「datas」という定数に置き換えています。

map関数でpropsを展開
              {datas.map(fanPage =>
    // 省略
              )}

map関数は配列の中身を展開することができるメソッドです。
先程dataをconsole.logで確認した際を思い出してください。

[ { id: 'JNTwv1d5W0g1yXQr667j',
    artistName: 'test1',
    body: 'test1',
    category: 'singer',
    monthlyFee: '1000',
    pageName: 'test1' },
  { id: 'aW749fTXcm99meV7x2WB',
    artistName: 'test2',
    body: 'test2',
    category: 'calligrapher',
    monthlyFee: '2000',
    pageName: 'test2' } ]

このようになっていました。この中身をオブジェクトごとに1つずつ展開することができるのです。

今はオブジェクト型データが2つだけしか入っていないためあまりメリットは感じられませんが、大量のデータを処理する際にはとても便利な関数です。

そして「datas.map(fanPage」と書くことによって、datasの中のオブジェクトを1つずつ「fanPage」という変数に格納します。

foreachでいうところの「foreach 配列 as 変数」という書き方に似ていますね。

mapで展開されたデータから値を参照
                <li key={fanPage.id}>
                    <Link href="/p/[detailid]" as={`/p/${fanPage.id}`}>
                      <a>{fanPage.artistName}</a>
                    </Link>
                </li>

上記のような書き方でリストレンダリングすることができます。

 { id: 'JNTwv1d5W0g1yXQr667j',
    artistName: 'test1',
    body: 'test1',
    category: 'singer',
    monthlyFee: '1000',
    pageName: 'test1' },

上記のようにオブジェクトにデータが入っているため、「fanPage.id」「fanPage.body」などといった書き方で個別にデータを参照することができるのです。

Linkの書き方について
 <Link href="/p/[detailid]" as={`/p/${fanPage.id}`}>

Linkの書き方にも注目しましょう。

hrefでは実際にpages/p/[detailid].jsというファイルにアクセスするということが明示されています。しかし実際にユーザーがURLで確認できるのは「/p/JNTwv1d5W0g1yXQr667j」などといった独自URLです。

このような書き方をすることによって動的なページを作成することができるようになっています。

動的なページの作成

次にURLごとにページの中身が切り替わる動的なページを作成していきます。

pages/p/[detail].jsを作成しましょう。

import { db } from '../../lib/db';
import React from 'react';

export default class Detail extends React.Component {
    static async getInitialProps({query}) {
        let result = await 
            db.collection("fanPages")
            .doc(query.detail)
            .get()
            .then(function(doc) {
                if (doc.exists) {
                    return doc.data();
                } else {
                    console.log('not exists');
                }
            }).catch(error => {
                console.log(error)
                return []
            })
          return {detail: result}
        }

      render() {
          const detail = this.props.detail;
        return (
            <React.Fragment>
                <div>
                    <h1>{detail.artistName}</h1>
                    <p>
                        {detail.body}
                    </p>
                    <ul>
                        <li>{detail.category}</li>
                        <li>{detail.monthlyFee}</li>
                        <li>{detail.pageName}</li>
                    </ul>
                </div>
            </React.Fragment>
          );
      }
}

これで以下のような画面が出てきたのではないでしょうか。
f:id:nekorokkekun:20190925114118p:plain

こちらも分けて解説をしていきます。

getInitialPropsでURLクエリパラメータから得たIDを元にデータを取得

まずはURLクエリパラメータから渡ってきたデータIDを元に、Firestoreから任意のデータを取得します。

先程扱ったgetInitialPropsと異なる点が何点かありますね。

getInitialPropsには引数がある

実はgetInitialPropsには動的なページ作成に必要な情報を引数として自動的に生成してくれる機能があります。

    static async getInitialProps({query}) {

上記の例では「query」という引数を受け取っています。
このqueryをconsole.logで見てみると…

{ detail: 'JNTwv1d5W0g1yXQr667j' }

と出力されました。
URLクエリパラメータでも使用した一意のデータIDですね。
このデータIDだけにアクセスしたければ「query.detail」と書いてあげればOKですね。

他にもgetInitialPropsには様々な引数があります。

Next.jsのドキュメントには以下のような記述がありました。

nitialProps receives a context object with the following properties:

pathname - path section of URL
query - query string section of URL parsed as an object
asPath - String of the actual path (including the query) shows in the browser
req - HTTP request object (server only)
res - HTTP response object (server only)
err - Error object if any error is encountered during the rendering
Documentation - Getting Started | Next.js

console.logで中を見てみるとかなり膨大な量のデータを引数で受け取れるということがわかりますので、こちらも検証してみてくださいね。

ドキュメントを指定したデータの取得
            db.collection("fanPages")
            .doc(query.detail)
            .get()
            .then(function(doc) {
                if (doc.exists) {
                    return doc.data();
                } else {
                    console.log('not exists');
                }

先程はコレクションのみの指定でしたが、さらにデータIDを引数にしてドキュメントを指定することができます。

MySQLで言う所の

Select * from fanPages where id = 1;

の「where」以降の条件付けとでも言えばいいでしょうか。

whereがなければテーブル(コレクション)全てのデータを取得することとなり、それは先程行った全件取得と同じですね。

そしてドキュメントの指定をする場合には「.doc(query.detail)」といった書き方が必要です。引数の「query.detail」には先程説明した通り、一意のデータIDが入っています。

後は先ほど説明したgetInitialPropsとほとんど変わりませんので割愛します。

render関数で描画

        const detail = this.props.detail;
        return (
            <React.Fragment>
                <div>
                    <h1>{detail.artistName}</h1>
                    <p>
                        {detail.body}
                    </p>
                    <ul>
                        <li>{detail.category}</li>
                        <li>{detail.monthlyFee}</li>
                        <li>{detail.pageName}</li>
                    </ul>
                </div>

propsで受けとったデータは再度、定数に格納して「detail.artistName」といった形で参照可能です。

これでFirestoreから任意のデータを取得して描画することができました。お疲れ様でした!

【Next.js】Firebase AuthenticationとHOCを使用してサインイン機能を実装する

f:id:nekorokkekun:20190925000003p:plain:w1000
本記事では、Next.jsプロジェクトにFirebase Authenticationを組み込み、サインイン機能を実装するとともに、HOC(高階コンポーネント)を使用してサインインユーザーのみが閲覧できるページを作成していきます。

Next.jsプロジェクトの準備

まずはNext.jsプロジェクトの準備をしていきましょう。

npx create-next-app next-firebase-auth
cd next-firebase-auth
yarn devhttp://localhost:3000/

http://localhost:3000/にアクセスしてみると以下のような画面が出てくるはずです。
f:id:nekorokkekun:20190924225728p:plain

サインインユーザーのみ見られるコンポーネントの作成

次にサインインユーザーのみが見ることのできるコンポーネントを作成していきましょう。

cd pages
touch dashboard.js

pages/dashboard.js

import React from 'react';
import Nav from '../components/nav';
class Dashboard extends React.Component {
 render() {
   return (
     <div>
          <Nav />
          <h1>Dashboard  Page</h1>
           <p>You can't go into this page if you are not authenticated.</p>
      </div>
     )
   }
}
export default Dashboard;

まだHOCに設定していないためアクセス可能です。

必要となるパッケージやDB接続ファイルの準備

次にFirebaseの接続に必要となるパッケージやDB接続用ファイルの準備をしていきます。

まずはルートディレクトリに.envファイルを作成します。

touch .env

ここにFirebaseでプロジェクトを作成した際に発行された

  • api key
  • auth domain
  • database url
  • project id
  • storage buchet
  • messaging sender id
  • app id

たちを定数として収めていきます。

Firebaseにおけるプロジェクトの作成から上記のキーの発行手順については以下の記事に書かれています。
nekorokkekun.hatenablog.com


取得したキーたちを以下のように.envに書き込んでいきましょう。
.env

FIREBASE_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_AUTH_DOMAIN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_DATABASE_URL=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_PROJECT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_STORAGE_BUCKET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_MESSAGING_SENDER_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
FIREBASE_APP_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


次にnext.config.jsをルートディレクトリ直下に作成します。

touch next.config.js

next.config.js

require("dotenv").config();
const path = require("path");
const Dotenv = require("dotenv-webpack");
module.exports = {
  webpack: config => {
    config.plugins = config.plugins || [];
    config.plugins = [
      ...config.plugins,
      // Read the .env file
      new Dotenv({
        path: path.join(__dirname, ".env"),
        systemvars: true
      })
    ];
    return config;
  }
};

最後にFirebaseへ接続するためのファイルを作成しましょう。

// ルートディレクトリから
mkdir src
cd src
mkdir firebase
cd firebase
touch index.js

src/firebase/index.js

import firebase from "firebase/app";
import "firebase/auth";
const config = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.FIREBASE_DATABASE_URL,
  projectId: process.env.FIREBASE_PROJECT_ID,
  storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.FIREBASE_APP_ID
};
if (!firebase.apps.length) {
  firebase.initializeApp(config);
}
const auth = firebase.auth();
export { auth, firebase };

これでprocess.env.〇〇(定数名)という形でプロジェクト内の他ファイルから.envで設定した定数名を使用するための下準備ができました!


そして以下の2つのパッケージをインストールすることで.envで設定した定数がprocess.env.〇〇(定数名)といった形でプロジェクト内の他のファイルからアクセス可能となります。

yarn add firebase dotenv
yarn add dotenv-webpack

FirebaseでAuthentication設定

次にFirebaseのコンソール上からAuthenticationの設定をしておきましょう。
先ほど作成したFirebaseのプロジェクトにアクセスします。
そしてサイドメニューの「Authentication」をクリックしましょう。
f:id:nekorokkekun:20190924232207p:plain


画像の順番に従い、
① タブメニューの「ログイン方法」をクリック
② 「有効にする」をオン
③ プロジェクトのサポートメールを設定
という順序で設定を完了させましょう。
f:id:nekorokkekun:20190924232600p:plain

index.jsの書き換え

独自のUIにしていますが、ポイントはfirebaseからauthをimportしているというところです。

import React from "react";
import Link from "next/link";
import Nav from "../components/nav";
import { auth, firebase } from "../src/firebase";
import Head from "next/head"

class Home extends React.Component {
  render() {
    return (
      <div>
        <Head title="Home" />
        <Nav />
        <div className="hero">
          <h1 className="title">
            Welcome to Firebase Authentication in Next.js!
          </h1>
          <p className="description">
            Click on the Dashboard link to visit the dashboard page.
          </p>
          <div className="row">
            <Link href="/dashboard">
              <a className="card">
                <h3>Go to Dashboard&rarr;</h3>
                <p>Visit Dashboard</p>
              </a>
            </Link>
          </div>
        </div>
        <style jsx>{`
          .hero {
            width: 100%;
            color: #333;
          }
          .title {
            margin: 0;
            width: 100%;
            padding-top: 80px;
            line-height: 1.15;
            font-size: 48px;
          }
          .title,
          .description {
            text-align: center;
          }
          .row {
            max-width: 880px;
            margin: 80px auto 40px;
            display: flex;
            flex-direction: row;
            justify-content: space-around;
          }
          .card {
            padding: 18px 18px 24px;
            width: 220px;
            text-align: left;
            text-decoration: none;
            color: #434343;
            border: 1px solid #9b9b9b;
          }
          .card:hover {
            border-color: #067df7;
          }
          .card h3 {
            margin: 0;
            color: #067df7;
            font-size: 18px;
          }
          .card p {
            margin: 0;
            padding: 12px 0 0;
            font-size: 13px;
            color: #333;
          }
        `}</style>
      </div>
    );
  }
}
export default Home;

HOC(高階コンポーネント)の作成

いよいよHOC(高階コンポーネント)を作成します。
高階コンポーネントについて今回は詳しく解説しませんが、「通常のコンポーネントに機能を追加するもの」という認識でOKです。

今回のHOCで追加する機能が「サインイン後に見ることができる」というものですね。

// src直下で
mkdir helpers
cd helpers
touch withAuth.js

src/helpers/withAuth.js

import React from "react";
import router from "next/router";
import { auth } from "../firebase";

const withAuth = Component => {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        status: "LOADING"
      };
    }
    componentDidMount() {
      auth.onAuthStateChanged(authUser => {
        if (authUser) {
          this.setState({
            status: "SIGNED_IN"
          });
        } else {
          router.push("/");
        }
      });
    }
    renderContent() {
      const { status } = this.state;
      if (status == "LOADING") {
        return <h1>Loading ......</h1>;
      } else if (status == "SIGNED_IN") {
        return <Component {...this.props} />;
      }
    }
    render() {
      return <>{this.renderContent()}</>;
    }
  };
};
export default withAuth;

こちらは細かく解説していきましょう。

引数でComponentを受ける

const withAuth = Component => {

後ほど「サインイン後しか見られないコンポーネント」を設定する際に

export default withAuth(コンポーネント名)

という書き方でexportします。

この「コンポーネント名」で指定されたコンポーネントが、withAuthの引数に設定された「Component」を指しています。

ユーザー認証されるまではstateで読み込みが「Loading」

    constructor(props) {
      super(props);
      this.state = {
        status: "LOADING"
      };
    }

stateの状態は基本的に「LOADING」(=見認証の状態)にしておき、
ユーザー認証されて初めてstateが「SIGNED IN」の状態となります。

authUserで認証の可否を判別

    componentDidMount() {
      auth.onAuthStateChanged(authUser => {
        if (authUser) {
          this.setState({
            status: "SIGNED_IN"
          });
        } else {
          router.push("/");
        }
      });
    }

サインインが成功した場合、「authUser」インスタンスの中には、ユーザー情報が入ることになります。そうするとif文でstateのstatusが「SIGNED_ID」になり、コンテンツを見ることができるようになります。

uid(=ユーザーID)やメールアドレスなどが入っているため、サインイン後にも利用できそうですね!

もしユーザーが未認証の場合にはauthUserは空のままのため、elseの方に行き、そのままトップページに遷移することとなります。

statusがLOADINGのままだと、LOADINGという画面が表示されることとなります。それが以下のものですね。

    renderContent() {
      const { status } = this.state;
      if (status == "LOADING") {
        return <h1>Loading ......</h1>;
      } else if (status == "SIGNED_IN") {
        return <Component {...this.props} />;
      }
    }

そしてstatusがSIGNED_INだと、HOCでサインインユーザーのみが見られるComponentを表示できるようになっています。

dashboard.jsへ追記

先ほど作成したdashboard.jsにwithAuth.jsを合体させる形で追記しましょう。

pages/dashboard.js

import React from 'react';
import Nav from '../components/nav';
// 追加
import withAuth from "../src/helpers/withAuth";

class Dashboard extends React.Component {
 render() {
   return (
     <div>
          <Nav />
          <h1>Dashboard  Page</h1>
           <p>You can't go into this page if you are not authenticated.</p>
      </div>
     )
   }
}
// withAuthの引数にdashboard.jsを追加
export default withAuth(Dashboard);

これでサインインユーザーのみがdashboard.jsを見られるような設定となりました!

index.jsに追記

次にサインインなどの処理を行うためのコードをpages/index.jsに追記します。

pages/index.js

import React from "react";
import Link from "next/link";
import Nav from "../components/nav";
import { auth, firebase } from "../src/firebase";
import Head from "next/head"

class Home extends React.Component {
// ここから下を追加
  handleSignIn = () => {
    var provider = new firebase.auth.GoogleAuthProvider();
    provider.addScope("https://www.googleapis.com/auth/contacts.readonly");
    auth
      .signInWithPopup(provider)
      .then(() => {
        alert("You are signed In");
      })
      .catch(err => {
        alert("OOps something went wrong check your console");
        console.log(err);
      });
  };
  handleSignout = () => {
    auth
      .signOut()
      .then(function() {
        alert("Logout successful");
      })
      .catch(function(error) {
        alert("OOps something went wrong check your console");
        console.log(err);
      });
  };
// ここまでを追加
  render() {
    return (
      <div>
        <Head title="Home" />
        <Nav />
        <div className="hero">
          <h1 className="title">
            Welcome to Firebase Authentication in Next.js!
          </h1>
          <p className="description">
            Click on the Dashboard link to visit the dashboard page.
          </p>
          <div className="row">
            <Link href="/dashboard">
              <a className="card">
                <h3>Go to Dashboard&rarr;</h3>
                <p>Visit Dashboard</p>
              </a>
            </Link>
   {/* ここから下2行を追加 */}
            <button onClick={this.handleSignIn}>Sign In using google</button> 
            <button onClick={this.handleSignout}>Signout</button>
          </div>
        </div>
        <style jsx>{`
          .hero {
            width: 100%;
            color: #333;
          }
          .title {
            margin: 0;
            width: 100%;
            padding-top: 80px;
            line-height: 1.15;
            font-size: 48px;
          }
          .title,
          .description {
            text-align: center;
          }
          .row {
            max-width: 880px;
            margin: 80px auto 40px;
            display: flex;
            flex-direction: row;
            justify-content: space-around;
          }
          .card {
            padding: 18px 18px 24px;
            width: 220px;
            text-align: left;
            text-decoration: none;
            color: #434343;
            border: 1px solid #9b9b9b;
          }
          .card:hover {
            border-color: #067df7;
          }
          .card h3 {
            margin: 0;
            color: #067df7;
            font-size: 18px;
          }
          .card p {
            margin: 0;
            padding: 12px 0 0;
            font-size: 13px;
            color: #333;
          }
        `}</style>
      </div>
    );
  }
}
export default Home;


こちらも少し解説をします。

以下はサインイン・サインアウトの処理を行うイベントの設定です。

handleSignInでは、ボタンを押すとサインインモーダルウィンドウがポップアップし、サインイン処理ができるという流れになります。

こちらは私たちが作り込む必要はありません。

  handleSignIn = () => {
    var provider = new firebase.auth.GoogleAuthProvider();
    provider.addScope("https://www.googleapis.com/auth/contacts.readonly");
    auth
      .signInWithPopup(provider)
      .then(() => {
        alert("You are signed In");
      })
      .catch(err => {
        alert("OOps something went wrong check your console");
        console.log(err);
      });
  };

handleSignoutも同じくで、Signout()メソッドを使えば簡単にユーザーを未承認の状態に戻してくれます。

  handleSignout = () => {
    auth
      .signOut()
      .then(function() {
        alert("Logout successful");
      })
      .catch(function(error) {
        alert("OOps something went wrong check your console");
        console.log(err);
      });
  };

そして、上記2つのイベントを呼び出すのが以下のbuttonですね。

            <button onClick={this.handleSignIn}>Sign In using google</button> 
            <button onClick={this.handleSignout}>Signout</button>

サインインをしてみる

それでは

yarn dev

でローカルサーバを起動して実際にサインインをしてみましょう。

まずはサインインしていない状態でDashboardに移ってみましょう。画面遷移しないですね。

次にサインインボタンをクリックします。

すると以下のようなウィンドウが出るはずです。
f:id:nekorokkekun:20190924235640p:plain


サインインのプロセスを経ると、Dashboardもみることができました。

最後にサインアウトできれば成功です!
お疲れ様でした。