Reactでアプリを作成しました【8】【Firebaseを使ったTodoリスト】

##Reactアプリの作成


```
% npm init react-app  <アプリ名>
```
パッケージをインストールする

```
% npm install
```

##Firebaseへデプロイ

#####① Firebase『使ってみる』をクリック

#####②『プロジェクトを作成』をクリックし、プロジェクト名を入力

#####③ googleアナリティクスを無効にして、『プロジェクトを作成』をクリック

#####④『続行』をクリックし、プロジェクトの概要『Hositing』を選択し、『始める』をクリック

#####⑤ Firebase CLIをインストール
* firebase -Vで、firebaseがインストールされているか確認する

```
$ npm install -g firebase-tools
```

#####⑥『次へ』をクリックし、プロジェクとの初期化『Google へのログイン』をインストール

```
$ firebase login
```
#####⑦『Firebase CLI が Google アカウントへのアクセスをリクエストしています』の表示が出るので、『許可』をクリック


#####⑧アプリケーションホルダーへ移動し、ルート ディレクトリから実行する

```
$ cd  <プロジェクト名>
$ firebase init
```

#####⑨ スペースキーで、下記を選択する

```
 Hosting: Configure files
 for Firebase Hosting and (optionally) set up GitHub Action deploys
```
```
Use an existing projectで、作成したプロジェクト名を選択する
```
```
buildを選択し、『No』を選択する
```
#####⑩FirebaseへデプロイするとHositing URL:が発行される

```
$ firebase deploy
```
```
$ npm run build
```
##Todoリスト作成
##Bootstrapのインストール

#####Adding Bootstrapのコマンドをインストールする

```
$ npm install --save bootstrap

$ npm install --save reactstrap react react-dom
```

```App.js
import 'bootstrap/dist/css/bootstrap.min.css';
```

##Formの作成 / App.jsを編集する

```App.js
import React, {useState} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
import { Container, Form, InputGroup, Input, InputGroupAddon, Button } from 'reactstrap';

function App() {
  const [value, setValue] = useState('')
  const [todos, setTodos] = useState([])

  const handleSubmit = e => {
    e.preventDefault()
    addTodo(value)
  }

  const addTodo = text => {
    const newTodos = [...todos, text]
    setTodos(newTodos)
  }
  return (
    <div className="App">
      <Container>
        <h1 className="mt-4">Todoリスト</h1>
        <Form onSubmit={handleSubmit}>
          <InputGroup>
            <Input type="text"
              value={value}
              onChange={e => setValue(e.target.value)} />
            <InputGroupAddon addonType="append">
              <Button type="submit" color="primary">追加</Button>
            </InputGroupAddon>
            </InputGroup>
        </Form>
     </Container>
    </div>
  );
}
export default App;
```

##リスト表示 + 削除ボタンの追加/完了・未完了変更処理の追加

```App.js
import React, {useState} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
import { Container, Form, InputGroup, Input, InputGroupAddon, Button } from 'reactstrap';

function App() {
  const [value, setValue] = useState('')
  const [todos, setTodos] = useState([])

  const handleSubmit = e => {
    e.preventDefault()
    addTodo(value)
    setValue('')
  }

  const addTodo = text => {
    //addTodoでデータを保存する際に、オブジェクトにして　completeというstatesを加える
    const newTodos = [...todos, { text, complete: false }]
    setTodos(newTodos)

  const removeTodo = index => {
    const newTodos = [...todos]
    //要素の削除には　splice を使用する　spliceは、配列のindex指定し、第二引数の渡した個数分を第一引数のindexを削除する　　
    //第一引数　indexを指定し 第二引数　1 を入力する
    newTodos.splice(index, 1)
   // 指定した要素を抜き取った新しい配列を再度、setTodosで指定する
    setTodos(newTodos)
  }
    
    const completeTodo = index => {
      //completeTodoの中では、statesがtrueの時はfalse  falseの時はtrue に変更するようにする
      const newTodos = [...todos]
      //引数として指定したindexを使用して、特定のtodoを指定する
      newTodos[index].complete = !newTodos[index].complete
      //更新されたTodoリストをセットして完了
      setTodos(newTodos)
      //違いがわかるようにconsole.logを記載
  }  
  return (
    <div className="App">
      <Container>
        <h1 className="mt-4">Todoリスト</h1>
        <Form onSubmit={handleSubmit}>
          <InputGroup>
            <Input type="text"
              value={value}
              onChange={e => setValue(e.target.value)} />
            <InputGroupAddon addonType="append">
              <Button type="submit" color="primary">追加</Button>
            </InputGroupAddon>
            </InputGroup>
        </Form>
     </Container>
      <Container>
        {/* tableタグを使ってリストを表示させる  */}
        <Table>
          <tbody>
            {/* 論理演算子を使い、処理がある時のみ実行する */}
            {todos && todos.map((todo, index) => (
              // keyにindexを与える
            <tr key={index}>
                <th className="text-left" style={{textDecoration: todo.complete ? "line-through" :""}}>
                  {todo.text}
                </th>
                {/* 親要素　textを右に寄せる */}
                <td className="text-right">
                  {/* //完了ボタンを作成　 onClickイベントを追加して、completeを実行する */}
                  {/* //statesの変更がわかるようにbuttonのtext/colorを変更する */}
                  <Button
                    {/* jsx内で三項演算子を使いtextを変更する */ }
                    color={todo.complete ? "secondary" : "success"}
                    className="mr-2"
                    onClick={() => completeTodo(index)}>
                    {todo.complete ? "完了" : "未完了"}
                  </Button>
                  {/* //削除ボタンを作成　 onClickイベントを追加して、removeを実行する　remove関数内にindexを使い　配列から要素を削除する*/}
                  <Button
                    color="danger"
                    onClick={() => removeTodo(index)}>
                    削除
                  </Button>
                </td>
            </tr>
              ))}
          </tbody>
        </Table>
     </Container>
    </div>
  );
}

export default App;
```
##Firebaseへログイン
#####状況を確認する

```
$ firebase login
```
#####react applicationをbuildする

```
% npm run build
```
#####Firebaseへアップロードする

```
% firebase deploy
```

##Todoアプリをコンポーネント化
#####components ディレクトリを src ディレクトリ配下に配置する
#####TodoForm TodoListコンポーネントを定義し、ファイルを用意する

```
$ mkdir src/components
$ touch src/components/TodoForm.js
$ touch src/components/TodoList.js
```
```App.js
import React, {useState} from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import './App.css';
import Container from 'reactstrap';
import TodoForm from './components/TodoForm';
import TodoList from './components/TodoList';

function App() {
  // const [value, setValue] = useState('')
  const [todos, setTodos] = useState([])

   {/* //Form　のソースコードを　TodoFormへ移行する */}
  // const handleSubmit = e => {
  //   e.preventDefault()
  //   addTodo(value)
  //   setValue('')
  // }

  const addTodo = text => {
    //addTodoでデータを保存する際に、オブジェクトにして　completeというstatesを加える
    const newTodos = [...todos, { text, complete: false }]
    setTodos(newTodos)

  const removeTodo = index => {
    const newTodos = [...todos]
    //要素の削除には　splice を使用する　spliceは、配列のindex指定し、第二引数の渡した個数分を第一引数のindexを削除する　　
    //第一引数　indexを指定し 第二引数　1 を入力する
    newTodos.splice(index, 1)
   // 指定した要素を抜き取った新しい配列を再度、setTodosで指定する
    setTodos(newTodos)
  }
    
    const completeTodo = index => {
      //completeTodoの中では、statesがtrueの時はfalse  falseの時はtrue に変更するようにする
      const newTodos = [...todos]
      //引数として指定したindexを使用して、特定のtodoを指定する
      newTodos[index].complete = !newTodos[index].complete
      //更新されたTodoリストをセットして完了
      setTodos(newTodos)
      //違いがわかるようにconsole.logを記載
  }  
  return (
    <div className="App">
      <Container>
        <h1 className="mt-4">Todoリスト</h1>
        {/* //TodoFormのコンポーネント化を行う */}
        <TodoForm addTodo={addTodo}/>
        {/* //TodoListコンポーネントで使用している　todos, completeTodo, removeTodo　を渡す */}
        <TodoList
          todo={todos}
          removeTodo={removeTodo}
          completeTodo={completeTodo}
        />
     </Container>
    </div>
  );
}
export default App;
```
```TodoList.js
import React from "react";
import { Button, Table } from "reactstrap";
//List内で使用している todos State   complete関数とremove関数をpropsで受け取る
function TodoList({todos, completeTodo, removeTodo}) {
    return (
    <Table>
       <tbody>
            {/* 論理演算子を使い、処理がある時のみ実行する */}
            {todos && todos.map((todo, index) => (
              // keyにindexを与える
            <tr key={index}>
                <th className="text-left" style={{textDecoration: todo.complete ? "line-through" :""}}>
                  {todo.text}
                </th>
                {/* 親要素　textを右に寄せる */}
                <td className="text-right">
                  {/* //完了ボタンを作成　 onClickイベントを追加して、completeを実行する */}
                  {/* //statesの変更がわかるようにbuttonのtext/colorを変更する */}
                  <Button
                            {/* jsx内で三項演算子を使いtextを変更する */ }
                    color={todo.complete ? "secondary" : "success"}
                    className="mr-2"
                    onClick={() => completeTodo(index)}>
                    {todo.complete ? "完了" : "未完了"}
                  </Button>
                  {/* //削除ボタンを作成　 onClickイベントを追加して、removeを実行する　remove関数内にindexを使い　配列から要素を削除する*/}
                  <Button
                    color="danger"
                    onClick={() => removeTodo(index)}>
                    削除
                  </Button>
                </td>
            </tr>
              ))}
          </tbody>
        </Table>
    )
}
export default TodoList;
```

```TodoForm.js
import React, { useState } from "react";
import { Form, InputGroup, Input, InputGroupAddon, Button } from "reactstrap";

//AddTodo関数をpropsとして受け取れるようにする
function TodoForm({ addTodo }) {
  const [value, setValue] = useState("");
  const handleSubmit = (e) => {
    e.preventDefault();
    addTodo(value);
    setValue("");
  };
  return (
    <Form onSubmit={handleSubmit}>
      <InputGroup>
        <Input
          type="text"
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
        <InputGroupAddon addonType="append">
          <Button type="submit" color="primary">
            追加
          </Button>
        </InputGroupAddon>
      </InputGroup>
    </Form>
  );
}
export default TodoForm;
```
##ContextAPIを使ったグローバルステート管理

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/6d345965-a926-bd4f-700b-78f0d7c0d3ab.png)

#####contexパターンの実装方法

#####contexts ディレクトリを src ディレクトリ配下に配置する
#####TodosContextsを定義し、ファイルを用意する

```
$ mkdir src/contexts
$ touch src/contexts/TodosContext.js
```

```App.js
import React from "react";
import "bootstrap/dist/css/bootstrap.min.css";
import "./App.css";
import Container from "reactstrap";
import TodoForm from "./components/TodoForm";
import TodoList from "./components/TodoList";

function App() {
return (
    <div className="App">
      <Container>
        <h1 className="mt-4">Todoリスト</h1>
        <TodosContextProvider>
          {/* //TodoFormのコンポーネント化を行う */}
          <TodoForm />
          {/* //TodoListコンポーネントで使用している　todos, completeTodo, removeTodo　を渡す */}
          <TodoList />
        </TodosContextProvider>
      </Container>
    </div>
  );
}
export default App;
```

```TodoList.js
import React, { useContext } from "react";
import { TodosContext } from "../contexts/TodosContext";
import { Button, Table } from "reactstrap";

function TodoList() {
  //useContextを使って関数とstateをTodoContextから呼び出す
  const { todos, completeTodo, removeTodo } = useContext(TodosContext);
  return (
    <Table>
      <tbody>
        {/* 論理演算子を使い、処理がある時のみ実行する */}
        {todos &&
          todos.map((todo, index) => (
            // keyにindexを与える
            <tr key={index}>
              <th
                className="text-left"
                style={{ textDecoration: todo.complete ? "line-through" : "" }}
              >
                {todo.text}
              </th>
              {/* 親要素　textを右に寄せる */}
              <td className="text-right">
                {/* //完了ボタンを作成　 onClickイベントを追加して、completeを実行する */}
                {/* //statesの変更がわかるようにbuttonのtext/colorを変更する */}
                <Button
                  color={todo.complete ? "secondary" : "success"}
                  className="mr-2"
                  onClick={() => completeTodo(index)}
                >
                  {todo.complete ? "完了" : "未完了"}
                </Button>
                {/* //削除ボタンを作成　 onClickイベントを追加して、removeを実行する　remove関数内にindexを使い　配列から要素を削除する*/}
                <Button color="danger" onClick={() => removeTodo(index)}>
                  削除
                </Button>
              </td>
            </tr>
          ))}
      </tbody>
    </Table>
  );
}
export default TodoList;
```


```TodosContext.js

import React, { createContext, useState } from "react";

export const TodosContext = createContext();
const TodoContextProvider = (props) => {
  const [todos, setTodos] = useState([])
  const addTodo = text => {
    //addTodoでデータを保存する際に、オブジェクトにして　completeというstatesを加える
    const newTodos = [...todos, { text, complete: false }]
      setTodos(newTodos)
      console.log(newTodos)

  const removeTodo = index => {
    const newTodos = [...todos]
    //要素の削除には　splice を使用する　spliceは、配列のindex指定し、第二引数の渡した個数分を第一引数のindexを削除する　　
    //第一引数　indexを指定し 第二引数　1 を入力する
    newTodos.splice(index, 1)
   // 指定した要素を抜き取った新しい配列を再度、setTodosで指定する
    setTodos(newTodos)
  }
    const completeTodo = index => {
      //completeTodoの中では、statesがtrueの時はfalse  falseの時はtrue に変更するようにする
      const newTodos = [...todos]
      //引数として指定したindexを使用して、特定のtodoを指定する
      newTodos[index].complete = !newTodos[index].complete
      //更新されたTodoリストをセットして完了
      setTodos(newTodos)
      //違いがわかるようにconsole.logを記載
      console.log(newTodos)
  }  
      return (
          <TodoContext.Provider value={{ todos, addTodo, removeTodo, completeTodo }}>
              {props.children}
          </TodoContext.Provider>
      );
};
export default TodoContextProvider;

```




```TodoForm.js

import React, { useState, useContext } from "react";
import { Form, InputGroup, Input, InputGroupAddon, Button } from "reactstrap";
import { TodosContext } from "../contexts/TodosContext";

function TodoForm() {
  //上記のuseContextの引数にContextオブジェクトを渡すことで、value値の値を使うことができる
  //addTodo関数を受け取る準備をし、useContextに渡すContextオブジェクトをインポートする
  const { addTodo } = useContext(TodosContext);
  const [value, setValue] = useState("");
  const handleSubmit = (e) => {
    e.preventDefault();
    addTodo(value);
    setValue("");
  };
  return (
    <Form onSubmit={handleSubmit}>
      <InputGroup>
        <Input
          type="text"
          value={value}
          onChange={(e) => setValue(e.target.value)}
        />
        <InputGroupAddon addonType="append">
          <Button type="submit" color="primary">
            追加
          </Button>
        </InputGroupAddon>
      </InputGroup>
    </Form>
  );
}
export default TodoForm;
```



##参考サイト
[最短・最速で学ぶ Firebase Hosting + React Todoアプリ実装編 (React Hooks)]
(https://www.udemy.com/course/firebase-hosting-todo-reactreact-hooks/learn/lecture/18800736#overview)
[firebase-toolsをインストールするために新しくanyenvのパスを通した件]
(https://e-yota.com/webservice/firebase-tools_install_anyenv/)