初心者がReact & Firebaseを使って収支管理アプリを作成（解説編）

#今回作ったもの
ログイン機能付きの収支管理アプリです。
いわゆる家計簿アプリ的な物です。
毎月の収入と支出をリスト化し月の残高を表示します。


【操作動画】　
*GIFなので画質悪くてすいません。
![GIF.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691178/8cf17867-eda3-6d79-f382-416700db7056.gif)


感想編については、別途記事を書いてるので、ご興味ございましたら是非ご覧下さいませ！
[初心者がReact学習歴1週間でWebアプリ作成に挑戦してみた](https://qiita.com/kana-wwib/items/6ef2653c63889b6348c1)

#コードについて
長くなってしまうので全部は載せておりません。
必要だと思うところだけ解説してます。
スタイルを基本CSSで装飾してますが、`className`は邪魔だと思うのでこの記事では消してます。
全コードはGithubに載せてます。
→[こちら](https://github.com/kana-wwib/My-budget-app)


#使用技術

- React（version 16.13.1）
 - Router
 - クラスの代わりにHooks (useState, useEffect, useContext)
- Firebase
 - Authentication
 - Cloud Firestore
 - Hosting

#Reactの準備
create react appで作成

```
npx create-react-app my-app
cd my-app
npm start
```

#Firebaseの準備
###プロジェクトを作成
![スクリーンショット 2020-10-05 午後3.47.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691178/a605548a-2c60-d332-28cf-f05a88af56e5.png)

作成方法は手順に従えば大丈夫です。
簡単なので割愛しますが、全体の流れはこちらのYoutubeを参考にしました。
[日本一わかりやすいReact入門【実践編】#3...Firebaseプロジェクトの作成と初めてのデプロイ](https://www.youtube.com/watch?v=ta2m6nfYHuQ&list=PLX8Rsrpnn3IVOk48awq_nKW0aFP0MGpnn&index=3)

注意点として、node.jsとfirebaseのバージョンにより少し挙動が動画内容と異なります。

ポイントは、

- ロケーション設定は初めにやる。東京なら「asia-northeast1」
- プランは現在の安定node versionだとSparkではなくBlazeプラン（従量制）になります
- Blazeプランなので、念の為Google Cloud Platformを作成して、アクセス& 料金状況/アラーム通知を受信できるよう設定します（任意）


###Authenticationの設定
Sign-in methodよりステータスを有効にする
今回は「メール/パスワード」を使用
![signinmethod.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691178/840bb6b8-0120-6acf-dfcc-180193f41d77.png)


###Cloud Firestoreでデータベース作成

- テストモードで実行
- とりあえずコレクション/ドキュメントを追加してみる（イメージのため）

最終的なコレクション/ドキュメント構成はこちらです。

![スクリーンショット 2020-10-05 午後3.50.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691178/8d6d38ee-7704-885c-c0ce-60e8ecbb6863.png)


#Firebaseの設定
Firebaseの情報をwebアプリに登録します。
プロジェクト内容は一応セキュリティを考慮し`.env`に登録します。
`.env`を`gitignore`すれば公開される心配がないということですね。

```.env
REACT_APP_FIREBASE_KEY="APIキー"
REACT_APP_FIREBASE_DOMAIN="プロジェクト.firebaseapp.com"
REACT_APP_FIREBASE_DATABASE="https://プロジェクト.firebaseio.com"
REACT_APP_FIREBASE_PROJECT_ID="プロジェクト"
REACT_APP_FIREBASE_STORAGE_BUCKET="プロジェクト.appspot.com"
REACT_APP_FIREBASE_SENDER_ID="ID番号"
```

`src`ディレクトリ直下に`firebase`ディレクトリと`Firebase.js`ファイルを作成します。
ここでFirebaseの初期化処理が行われます。
`auth`と`db`も作り、毎回全部書かなくて済むようにします。

```JavaScript:src/firebase/Firebase.js

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

firebase.initializeApp({
  apiKey: process.env.REACT_APP_FIREBASE_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID
});

const auth = firebase.auth();
const db = firebase.firestore();

export { auth, db }
```



#ログイン機能の実装

`auth`ディレクトリを作成し、こちらに認証機能系は集約させます。

ディレクトリ構成

```
src
 ├── auth
    ├── AuthProvider.js
    └── Login.js
    └── PrivateRoute.js
    └── SignUp.js
 ├── components
 ├── firebase
 ├── App.js

```

まず、`App.js`にログイン状態で表示ページを変える為、`Router`を作ります。
`exact`はpathが「含む」とならないように指定。

```JavaScript:src/App.js
const App = () => {
  return (
    <AuthProvider>
      <Router>
        <Switch>
          <PrivateRoute exact path="/" component={Home} />
          <Route exact path="/login" component={Login} />
          <Route exact path="/signup" component={SignUp} />
        </Switch>
      </Router>
    </AuthProvider>
  );
};

export default App;
```


###AuthPrivider.js
認証の情報（ユーザーがログイン、サインアップする）は、こちらで作ります。
そしてユーザー情報が必要なコンポーネントで`useContext`を使います。
通常データはトップダウン形式で`props`を渡さないといけないですが、
`context`を使うことで、コンポーネントツリーに簡単にデータを共有することができます。

`useContext`についてこちらの記事が非常にわかりやすかったです。
[useContextの使い方](https://www.sunapro.com/usecontext/)
[こんなに簡単なの？React useContextって](https://reffect.co.jp/react/react-usecontext-understanding)

```JavaScript:src/auth/AuthProvider.js
import React, { useEffect, useState } from "react";
import { auth } from "../firebase/Firebase";

const AuthContext = React.createContext() 

const AuthProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(null);

  //サインアップ後認証情報を更新
  const signup = async (email, password, history) => {
    try { 
      await auth.createUserWithEmailAndPassword(email, password);
      auth.onAuthStateChanged(user => setCurrentUser(user));
      history.push("/");
    } catch (error) {
      alert(error);
    }
  };

  //ログインさせる
  const login = async (email, password, history) => {
    try {
      await auth.signInWithEmailAndPassword(email,password);
      auth.onAuthStateChanged(user => setCurrentUser(user));
      history.push("/");
    } catch (error) {
      alert(error);
    }
  }

  //初回アクセス時に認証済みかチェック
  useEffect(() => {
    auth.onAuthStateChanged(setCurrentUser);
  }, []);

  return (
    <AuthContext.Provider value={{ signup, login, currentUser}}>
      {children}
    </AuthContext.Provider>
  )
}

export {AuthContext, AuthProvider}
```
初期値のユーザーのステートは`null`ですね。

**`signup`関数**
引数に`email`、`password`、`history`を渡して非同期処理を行います。
`auth.createUserWithEmailAndPassword(email, password)`は、
firebaseのメソッドで`email`と`password`を元にアカウントが作成されます。
その後`user`の情報を取得し、`CurrentUser`にセットします。

`history.push（"/")`は、reactRouterの画面遷移させる機能です。
今回はログイン後、Home画面に行きます。

**`login`関数**
同じ様に、ユーザーがログインしたら情報を取得し`CurrentUser`を更新するようにします。
`auth.signInWithEmailAndPassword(email,password)`
これもまたfirebaseのメソッドです。

あとは、最初にログインしてるか確認する為、`useEffect`で一回だけ`auth.onAuthStateChanged`を実行します。
※一回だけ実行したいので、第二引数には空の配列`[]`を渡します。

###PrivateRoute.js
アプリのメイン画面`Home.js`は、`PrivateRoute`に指定します。
ここで、ユーザーがログインしてれば→メイン画面を表示。
未ログインの場合→ログイン画面を表示。の作業を行なってます。

```JavaScript:src/auth/PraveteRoute.js
import React, { useContext } from "react";
import { Route } from "react-router-dom";
import { AuthContext } from "./AuthProvider";
import Login from "./Login";

const PrivateRoute = ({ component, ...rest}) => {
  const { currentUser } = useContext(AuthContext);
  //AuthContextからcurrentUserを受け取る

  const Component = currentUser ? component : Login;
  //currentUserがtrueの場合component＝Home、falseならLoginコンポーネントにroute

  return <Route {...rest} component={Component} />;
};

export default PrivateRoute;

```
`...rest`は、残りのpropsをまとめて引数に渡してます（今回他のpropsはないですが）
この`...`ですが、以前RestParametersの記事を書きましたのでよろしければご参照ください。
ES6の新しい構文です！
[スプレッド構文とRestパラメータを理解する](https://qiita.com/kana-wwib/items/0ee38fbcda4c9a45163b)

###SignUp.jsとLogin.js

**SignUp.js**コンポーネントでは、ユーザー登録画面の表示、登録内容を取得します。
`handleSubmit`が実行される時、入力された`email`と`password`の内容を`AuthProvider`で作った`signup`関数の引数に渡してデータが登録されます。
アップデート後のhistory（情報）を渡すために、最後exportの時`withRouter(SignUp)`を使っています。


```JavaScript:src/auth/SignUp.js
const SignUp = ({ history }) => {
  const { signup } = useContext(AuthContext);
  //AuthContextからsignup関数を受け取る
  
  const handleSubmit = event => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    signup(email.value, password.value, history);
  };

  return (
      <div>
        <h1>Sign Up</h1>
        <form onSubmit={handleSubmit}>
          <div>
            <label>E-mail Address</label>
            <input name="email" type="email" placeholder="email@gmail.com" />
          </div>
          <div>
            <label>Password</label>
            <input name="password" type="password" placeholder="Password"/>
          </div>
            <SignUpButton type="submit">SIGN UP</SignUpButton>
        </form>
        <Link to="/login">SignInへ戻る</Link>
      </div>
    </div>
  );
};

export default withRouter(SignUp);

```

**Login.js**も似たような感じで作ります。
`signup`部分を`login`に変えるだけですね。

```JavaScript:src/auth/Login.js
const Login = ({ history }) => {
  const { login } = useContext(AuthContext);
  //AuthContextからlogin関数を受け取る

  const handleSubmit = event => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    login(email.value, password.value, history);
  };

```


#コンポーネント・ファイル構成
続いてメイン画面です。
アプリのメイン画面を担うコンポーネント達がこちら。
メイン（親）のコンポーネントは、**Home.js**になります。

```
src
 ├── auth
 ├── components
    ├── Home.js
    └── Header.js
    └── Balance.js
    └── IncomeExpense.js
    └── AddItem.js
    └── ItemsLists.js
    └── IncomeItem.js
    └── ExpenseItems.js
    └── TotalIncomeExpense.js //共通関数ファイル
 ├── firebase
 ├── App.js

```


画面上ではこんな感じ。
![main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691178/3d0e3540-683f-7426-bfd6-66b024a890dd.png)


#ステートの作成/更新
親コンポーネントから子コンポーネントに渡す為、ステートは全て`Home.js`で作成します。

作成したステート達

```JavaScript:src/compoments/Home.js
  const [inputText, setInputText] = useState("");
  const [inputAmount, setInputAmount] = useState(0);
  const [incomeItems, setIncomeItems] = useState([]);
  const [expenseItems, setExpenseItems] = useState([]);
  const [type, setType] = useState("inc");
  const [date, setDate] = useState(new Date());
```
収入incomeの配列`incomeItems`と、支出expenseの配列`expenseItems`は、後々計算が楽なので分けて作成。

###データを追加する

Firestoreからデータを取得・追加は全て`Home.js`で行います。

こちらは追加バージョン

```JavaScript:src/compoments/Home.js
  const addIncome = (text, amount) => {
    const docId = Math.random().toString(32).substring(2);
    const date = firebase.firestore.Timestamp.now();
    db.collection('incomeItems').doc(docId).set({
      uid: currentUser.uid,
      text,
      amount,
      date,
    });
    .then(response => {
      setIncomeItems([
        ...incomeItems, {text: inputText, amount: inputAmount, docId: docId , date: date}
      ]); 
    });
  }
```

- 収入income用の関数`addIncome`を用意。引数にはユーザーが入力した`text`と`amount`を渡す。
- `docId`をこちらで作る。
- 収入リストが順番に並べられるように`date`を作成。`firebase.firestore.Timestamp.now()`（入力時間が登録される）→firebaseのメソッド
- どこのcollectionのdocumentに追加するかは、firebaseのメソッドを使用`db.collection('incomeItems').doc(docId).set({})`
- setしたいデータを配列として追加`uid ~ date`
- その後`.then`で、reactアプリ側の`setIncomeItems`のステートを更新

ポイントは、
ユーザーが削除ボタンを押した時、データを削除するのに`docId`を使います。
reactアプリと連動させたいので、こちら側で手動で作成してます。
※その時、数値だとエラーになるので文字列に変換`.toString(32).substring(2)`
手動で作らない場合は、「.set」ではなく「.add」で自動生成可能。

これと同じ内容で出費expense用の関数も用意すれば、値はFirestoreとReact上で無事追加/更新されます。


###データを取得する
Firestoreからデータを取ってきて、アプリ上で表示させます。

```JavaScript:src/compoments/Home.js
  const getIncomeData = () => {
    const incomeData = db.collection('incomeItems')
    incomeData.where('uid', '==', currentUser.uid).orderBy('date').startAt(startOfMonth(date)).endAt(endOfMonth(date)).onSnapshot(query => {
      const incomeItems = []
      query.forEach(doc => incomeItems.push({...doc.data(), docId: doc.id}));
      setIncomeItems(incomeItems);
    });
  }
```
- 取得したいデータのIncome用の関数`getIncomeData`を作成
- コレクション`incomeItems`のドキュメントを取得→変数`incomeData`に代入
- `uid`が現在のユーザーと一致する場合の`startOfMonth`~`endOfMonth`のドキュメントを取得
- 取得したデータを保存する空の配列`incomeItems`を作成
- その配列にドキュメントの`data`と`id`を`push`（追加）する
- reactアプリ側の`incomeItems`の配列を更新する

ポイントは、
`orderBy`のメソッドで`date`を昇順にしてます。
リストがバラバラに表示されてしまうので、制御する為に必要です。
また、`orderBy`で昇順にしようとするとfirebaseから「indexを作れ」というエラーが出ます。
その際、親切にURLが表示されるのでそこにアクセスしてindexを作ればOKです（結構時間かかります）
参考記事：[複合index](https://qiita.com/zaburo/items/1905499ceab8c1420593)

`startAt`と`endAt`は、その月の分だけ表示させる為です。
この引数の中身については、後ほど詳細を書きます。

これと同じ内容で出費expense用の関数も用意すれば、無事Firestoreからデータを取得できて、Reactのステートに更新/表示がされます。

尚、データを取得するタイミングは、`useEffect`を使って操作します。

1.  最初の１回のみ実行してほしい。引数は空の配列。
2. `date`が更新されるたびに実行してほしい。※`date`は次で解説してますがヘッダーの月の部分です。
 
```JavaScript:src/compoments/Home.js
  useEffect (() => {
    getIncomeData();
    getExpenseData();
  }, []);

  useEffect(() => {
    getIncomeData();
    getExpenseData();
  }, [date]);
```

#月ごとにデータを表示させる
月ごとにデータを分けて表示させる為、ステートで作った`date`を使います。
初期値は現在の日時が入ってます。

```JavaScript:src/compoments/Home.js
const [date, setDate] = useState(new Date());
```

###ヘッダーに渡して表示
`date`は、ユーザーがヘッダーの"前月"か"次月"ボタンを押すと更新されます。
↓の関数で月の部分を変えてます。

```JavaScript:src/compoments/Home.js
  //for Header
  const setPrevMonth = () => {
    const year = date.getFullYear();
    const month = date.getMonth()-1;
    const day = date.getDate();
    setDate(new Date(year, month, day));
  }

  const setNextMonth = () => {
    const year = date.getFullYear();
    const month = date.getMonth()+1;
    const day = date.getDate();
    setDate(new Date(year, month, day));
  }
```

これらは`Header.js`で使うので`props`で渡してあげます。

```JavaScript:src/compoments/Home.js
<Header 
  date={date}
  setPrevMonth={setPrevMonth}
  setNextMonth={setNextMonth}
/>
```

`Header.js`では、現在の月を表示させる為、`year`と`month`を作り、
隣に前月と次月ボタンを表示させます。

```JavaScript:src/compoments/Header.js
  const today = date;
  const year = today.getFullYear();
  const month = today.getMonth()+1;

  return (
    <div className="head">
      <SignOutButton onClick={() => auth.signOut()}>Sign Out</SignOutButton>
      <div>
        <button onClick={() => setPrevMonth()}>←前月 </button>
        <h1>{year}年{month}月</h1>
        <button onClick={() => setNextMonth()}> 次月→</button>
      </div>
    </div>
  )
```
これでボタンをクリックしたら、`setPrevMonth()`と`setNextMonth()`が実行され、月の表示が変わります。


#ユーザーの入力内容を操作する
ユーザーが入力した内容を取得する関数は、`AddItem.js`コンポーネントで行ってます。

```JavaScript:src/components/AddItem.js
export const AddItem = ({ addIncome, addExpense, inputText, setInputText, inputAmount, setInputAmount, type, setType, selectedMonth, thisMonth}) => {

  const typeHandler = (e) => {
    setType(e.target.value);
  }

  const inputTextHandler = (e) => {
    setInputText(e.target.value);
  };

  const inputAmountHandler = (e) => {
    setInputAmount(parseInt(e.target.value));
  }

  const reset = () => {
    setInputText("");
    setInputAmount("");
  }

  const submitItemHandler = (e) => {
    e.preventDefault();
    if (inputText == '' || inputAmount == '0' || !(inputAmount > 0 && inputAmount <= 10000000)) {
      alert ('正しい内容を入力してください')
    } else if ( type === 'inc') {
      addIncome(inputText, inputAmount) 
      reset();
    } else if ( type === 'exp' ) {
      addExpense(inputText, inputAmount)
      reset();
    }
  }

  const thisMonthForm = () => {
    return (
      <form>
        <select onChange={typeHandler}>
          <option value="inc">+</option>
          <option value="exp">-</option>
        </select>
        <div>
          <label>内容</label>
          <input type="text" value={inputText} onChange={inputTextHandler}/>
        </div>
        <div>
          <label>金額</label>
          <input type="number" value={inputAmount} onChange={inputAmountHandler}/>
          <div>円</div>
        </div>
        <div>
        <AddButton type="submit" onClick={submitItemHandler}>追加</AddButton>
        </div>
      </form> 
    )
  }

  const otherMonthForm = () => {
    return (
      <form></form>
    )
  }
        
  return (
    <>
    {thisMonth === selectedMonth ? thisMonthForm() : otherMonthForm()}
    </>
  )
  
} 
```

- 収入income、出費expense、どちらに追加するのかは`type`で分けてます。
- ユーザーが、`option` を選択した時に`typeHandler`関数で`type`の値を更新します。
- `input`の値と`amount`の値も`onChange`で取得します。
- `amount`については、後に計算するので`parseInt`で値を数値化します。

`submitItemHandler`で追加ボタンが押された時、操作してる内容はこちら。

- デフォルトのイベント（動作）をキャンセル
- 正しい内容が入力されてない場合、エラーを表示
- `inc`タイプなら、`Home.js`で定義した`addIncome`の引数に
ユーザーの入力内容`inputText`と`inputAmount`を渡す
- `exp`タイプも同様

→Firestoreのデータが追加され、reactアプリのステートも更新されるという流れ。



ヘッダーが今月なのか、今月でない月かによって表示方法を変える為、条件付きレンダーを行ってます。
（※今月のみ追加フォームを表示させる為）


この条件に使ってる`selectedMonth`と`thisMonth`は、
他のコンポーネント（リスト）でも使うので、`Home.js`で作ってpropsで渡してます。

```JavaScript:src/components/Home.js
  //operate add form and income/expense list
  const selectedMonth = date.getMonth() + 1;
  const today = new Date();
  const thisMonth = today.getMonth() + 1;
```

#リストの表示
リストの表示は`ItemsList.js`を作り、そこでitemsに対して`map`を行い、`IncomeItem`と`ExpenseItem`をそれぞれ表示します。

```JavaScript:src/components/ItemsList.js
export const ItemsList = ({ deleteIncome, deleteExpense, incomeItems, expenseItems, incomeTotal, selectedMonth, thisMonth}) => {

  return (
    <div>
      <div>
        <h3>収入一覧</h3>
          <ul>
            {incomeItems.map((incomeItem) => (
              <IncomeItem 
                deleteIncome={deleteIncome}
                incomeText={incomeItem.text}
                incomeAmount={incomeItem.amount}
                incomeItem={incomeItem}
                key={incomeItem.docId}
                selectedMonth={selectedMonth}
                thisMonth={thisMonth}
              />
            ))}
          </ul>
      </div>
      <div>
        <h3>支出一覧</h3>
        <ul>
            {expenseItems.map((expenseItem) => (
              <ExpenseItem
                deleteExpense={deleteExpense}
                expenseText={expenseItem.text}
                expenseAmount={expenseItem.amount}
                expenseItem={expenseItem}
                key={expenseItem.docId}
                incomeTotal={incomeTotal}
                selectedMonth={selectedMonth}
                thisMonth={thisMonth}
              />
            ))}
          </ul>
      </div>
    </div>
  )
}
```

`map`を実行して作られた一つ一つの項目を表示されるコンポーネントがこちら↓

**Incomeバージョン**

```JavaScript:src/components/IncomeItem.js
export const IncomeItem = ({ deleteIncome, incomeItem, incomeText, incomeAmount, thisMonth, selectedMonth}) => {

  const deleteHandler = () => {
    deleteIncome(incomeItem.docId);
  }

  const showThisMonth = () => {
    return (
      <li>
      <div>{incomeText}</div>
      <div>+{Number(incomeAmount).toLocaleString()}円</div>
      <button onClick={deleteHandler}>×</button>
      </li>
    )
  }

  const showPastMonth = () => {
    return (
      <li>
      <div>{incomeText}</div>
      <div>+{Number(incomeAmount).toLocaleString()}円</div>
      </li>
    )
  }

  return (
    <>
      {thisMonth === selectedMonth ? showThisMonth() : showPastMonth()}
    </>
  )
}
``` 
`Number(incomeAmount).toLocaleString()`は、カンマ「,」を表示させる為。
`deleteHandler`は "×" を押した時に`dleteIncome`を実行してます。
この`delteIncome`は、fireStoreの`docId`の関係上、`Home.js`で定義されてます。↓
引数にこの`incomeItem.docId`を渡せば該当のアイテムは削除されます。

```JavaScript:src/compoments/Home.js
  const deleteIncome = (docId) => {
    db.collection('incomeItems').doc(docId).delete();
  }

```

firebaseのメソットを使い、`incomeItems`コレクションにある、該当ドキュメントを削除してます。

あとは、スタイル上、表示の仕方を変えたいので、ここでも条件付きレンダーを行ってます。
→　`showThisMonth`と`showPastMonth`で分ける。
これで今月以外、削除ボタンを表示させないようにしてます。

これと同じようにexpenseバージョンも作ればOK

#収入/支出の値を計算する

###収入と支出の各合計
値の計算はそれぞれの`incomeItems`と`expenseItems`の配列から、`amount`を取り出して計算します。

```JavaScript:src/components/IncomeExpense.js
export const IncomeExpense = ({ incomeTotal, expenseItems }) => {

  const expenseAmounts = expenseItems.map(expenseItem => expenseItem.amount);

  const expenseTotal = expenseAmounts.reduce((acc, cur) => acc += cur, 0);

  const percentage = () => {
    if (incomeTotal >= 1) {
      return `${Math.round((expenseTotal / incomeTotal) * 100)} %`;
    } else {
      return '---';
    }
  };
  
  return (
    <div>
      <div>
        <h2>収入</h2>
        <div>
          <p>+ {Number(incomeTotal).toLocaleString()}<span> 円</span></p>
      </div>
        </div>
      <div>
        <h2>支出</h2>
        <div>
          <p>- {Number(expenseTotal).toLocaleString()}<span> 円</span></p>
          <div>{percentage()}</div>
        </div>
      </div>
    </div>
  )
}
```
`map`と`reduce`については、こちらを元に別で記事を書いています。
※`filter`は途中色々変えたので、結局今回のアプリに使っていません。

[map・filter・reduceの書き方・使い方](https://qiita.com/kana-wwib/items/3acb6d448aef63748be7)

**支出expenseの合計計算**
- `expenseAmount`に`expenseItems`の中の`amount`だけ取り出した配列を代入します。
- `expenseAmount`を使って、累計を計算し、`expenseTotal`とします。

これを収入`incomeItems`でも同じことをします。
ただincomeバージョンについては、他のコンポーネントでも使うので、共通関数ファイル`TotalIncome.js`を作成してます。

`TotalIncome.js`で関数`totalCalc`を作り、`Home.js`で`incomeItems`を引数に渡してます。

```JavaScript:src/components/Home.js
  // calculate % and show total
  const incomeTotal = totalCalc(incomeItems);
```

```JavaScript:src/components/TotalIncome.js
export const totalCalc = (incomeItems) => {
  const incomeAmounts = incomeItems.map(incomeItem => incomeItem.amount);
  return incomeAmounts.reduce((acc, cur) => acc += cur, 0);
};
```
こちらを使う他のコンポーネントとは、
割合%を表示する`IncomeExpense.js`、`ExpenseItem`と、計算に必要な`Balance.js`になります。

###残高の計算
総合計の残高を計算をする`Balance.js`コンポーネントでは、
`IncomeTotal - ExpenseTotal`をすれば計算できます。

```JavaScript:src/components/Balance.js
const balance = incomeTotal - expenseTotal
```

#アプリを公開
あとは公開するだけです！
私の場合は`firebase login`と`init`は先に済ませてました。

```
> firebase login
> firebase init
- 選択項目は FireStore, Functions, Hosting
- publicディレクトリはbuildにする
> npm run build
> firebase deploy
```


#あとがき
今回のアプリは全てデータをpropsで親から子供に渡しているので、
どうしてもメインの`Home.js`が少しボリューミーになってしまいました。（そういうものなのか？）
初心者が書いたコードですので、ご理解いただければと思います。
こうした方が良い等ありましたら、是非ご指摘お願いします！
日々勉強して、もっと良い書き方でコードを書けるよう頑張ります。

#参考

- ログイン機能
[Firebase公式](https://firebase.google.com/docs/auth/web/start?hl=ja)
[Firebase React Authentication Tutorial For Beginners - Private Route With Hooks](https://youtu.be/unr4s3jd9qA)
[【React】 Firebaseを使用して認証機能の実装](https://qiita.com/k-penguin-sato/items/6e892231922b360a8659#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D)
[ReactHooks + Firebase(Authentication, Firestore)でTodoアプリ作る](https://qiita.com/k_tada/items/ed05d14458d1ddfcefae)

- Firebaseデータ関連
[FireStore公式:データ追加](https://firebase.google.com/docs/firestore/manage-data/add-data?hl=ja)
[FireStore公式:データ取得](https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja)
[FireStore公式:データ削除](https://firebase.google.com/docs/firestore/manage-data/delete-data?hl=ja)