Reactでアプリを作成しました【5】【Todo List ②】

##【仕様】
- テキスト入力欄にタスクを書き込んでエンターキーを押すと下のリストに追加される。
- 完了したタスクは、チェックボックスを『ON』にする。視覚効果として、完了済みのタスクは文字を薄くなる。
- タブによるフィルタリング機能もあり、『All』を選択すると全てのタスク、『ToDo』を選択すると未完了のタスクのみ、『Done』を選択すると完了済みのタスクのみが表示される。
- 下部には表示中のタスク件数が表示される。

##【コンポーネント設計する】

#####アプリの作成（実装）を始める前に、どうアプリを作成するのかを下記の点を踏まえ、考える。
- どのようにコンポーネントを分けるか。
- それぞれのコンポーネントはどのような属性（props）を受け取り、どのような状態（state）を管理するか。

#####※ 『フォーム』や『ボタン』『リンク』『タブ』などインタラクティブな UI 要素がある場合、『ハンドラ』が必要。　誰が『ハンドラ』を定義して、誰が実行するかを考えること。

##1. 作成する『Todoアプリ』のコンポーネントを設計する
#####① 『Todo』コンポーネントは、アプリの全体　
➡︎　アプリの大元になるので、外部からの props は不要。
#####② 『Input』コンポーネントは、タスクの入力欄
➡︎  入力欄なので、入力値を『state』として管理する。この入力値は、エンターキーが押されるまで親（Todo）が知る必要はないので、『Input』コンポーネント内で管理するが、エンターキーが押されたら、親のタスクリストを更新する必要がある。
#####③ 『Filter』コンポーネントは、 タブのフィルタリング部分
➡︎ 『Input』と同様、それぞれのタブが押された時のイベントハンドラ関数を『props』 として受け取る必要がある。そのほか、選択中のタブは見た目を変える必要がある。

#####④ 『TodoItem』 コンポーネントは、タスク一個分
➡︎ 一個分のタスク情報を親から『props』で渡してもらう必要がある。さらに、チェックボックスがあり、チェックすると、何らかの形でタスクの完了状態を更新する。『TodoItem』はタスク情報をもらうだけで、自分では管理しない。管理するのは『 Todo』で、子でイベントが発生したタイミングで、親の『state』が更新される必要があるので、『Todo』から『TodoItem』にイベントハンドラを渡すパターンが良い。言い換えるなら、『TodoItem』は、どのタスクがチェックされたか、親である『Todo』 知らせる必要がある。
#####※「知らせる」とは「イベント」のこと

###Propsとは
[Reactを基本からまとめてみた【5】【Props（プロップス）】]
(https://qiita.com/kanfutrooper/items/c0e75dc3c2ad8cbd32e5)

###Stateとは
[Reactを基本からまとめてみた【6】【State（ステート）】]
(https://qiita.com/kanfutrooper/items/340760f49fc253f612da)


##2. HTMLを作成する

```index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>⚛️ React ToDo</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.8.2/css/bulma.min.css" />
  <style>
    .container { margin-top: 2rem; }
  </style>
</head>
<body>
  <div id="root"></div>

  <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
  <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.6/index.min.js"></script>
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

  <script type="text.js">

    // ここにコードを書いていく
  
  </script>
</body>
</html>
```

###CSS フレームワーク　『Bulma』　とは
公式サイト : [Bulma]
(https://bulma.io/)

『Bulma』は、 JavaScript 無しのプレーンな CSS フレームワーク。予め用意されたコンポーネントに対応するクラス名をHTML要素に付与することでスタイリングを行う。

###React ライブラリ 『classnames』  とは
パッケージドキュメント : [classnames]
(https://www.npmjs.com/package/classnames#usage-with-reactjs)
htmlではclassを使ってスタイリングをするが、Reactが使っているjsxではclassNameを使ったスタイリングを行う。

##3. レンダリングされるルートコンポーネントとして『App』を作成する

```text.js
function Todo() {
  return null;
}

function App() {
  return (
    <div className="container is-fluid">
      <Todo />
    </div>
  );
}

const root = document.getElementById('root');
ReactDOM.render(<App />, root);
```

##4. タスクを表示する機能を作成する

#####①Todo に以下の state を生成する。
タスクは複数存在するので、配列で表現する。

```
const [items, setItems] = React.useState([]);
```
この配列に入るのはデータは、タスクの文字列をそのまま配列に入れると、完了したかどうか分からないので、以下の形式で管理する。

```
{
  key: String,
  text: String,
  done: Boolean
}
```
key は、タスクを一意に特定する ID で、本格的なアプリだとデータベースに格納した結果の ID 値などになるが、今回は以下の関数でランダムな文字列を生成する。
この関数はコードの一番上に追加する。

```
const getKey = () => Math.random().toString(32).substring(2);
```

#####② state の初期値にテストデータを入れる。
その state をループで表示する JSX コードを追加する。

```text.js
function Todo() {
  const [items, setItems] = React.useState([
    { key: getKey(), text: 'Learn JavaScript', done: false },
    { key: getKey(), text: 'Learn React', done: false },
    { key: getKey(), text: 'Get some good sleep', done: false },
  ]);

  return (
    <div className="panel">
      <div className="panel-heading">
        ⚛️ React ToDo
      </div>
      {items.map(item => (
        <label className="panel-block">
          <input type="checkbox" />
          {item.text}
        </label>
      ))}
      <div className="panel-block">
        {items.length} items
      </div>
    </div>
  );
}
```

#####③ タスクの部分を『TodoItem』コンポーネントに切り出す。

```
function TodoItem({ item }) {
  return (
    <label className="panel-block">
      <input type="checkbox" />
      {item.text}
    </label>
  );
}
```

TodoItem から返される JSX は以下のようになる。
ループで生成される要素には『key』が必要で、タスクに用意した『key』プロパティを利用する。

```
<div className="panel">
  <div className="panel-heading">
    ⚛️ React ToDo
  </div>
  {items.map(item => (
    <TodoItem key={item.key} item={item} />
  ))}
  <div className="panel-block">
    {items.length} items
  </div>
</div>
```

##5.タスクの完了状態を切り替える
#####①チェックボックスで完了状態を切り替えられるようにする。
TodoItem では、チェックされた（もしくは外された）時に、ハンドラ関数 onCheck を実行する。ハンドラにはタスク情報を渡す。子から親に知らせるイメージ。

```
function TodoItem({ item, onCheck }) {
  const handleChange = () => {
    onCheck(item);
  };

  return (
    <label className="panel-block">
      <input
        type="checkbox"
        checked={item.done}
        onChange={handleChange}
      />
      {item.text}
    </label>
  );
}
```

#####②Todo にハンドラを実装する。
items から map で新しいリストを作成して setItems する。map の中では、key で同一判定をして、チェック対象の done の真偽を反転させる。

```
const handleCheck = checked => {
  const newItems = items.map(item => {
    if (item.key === checked.key) {
      item.done = !item.done;
    }
    return item;
  });
  setItems(newItems);
};
```

#####③実装したハンドラを TodoItem の onCheck props に指定する。

```
{items.map(item => (
  <TodoItem
    key={item.key}
    item={item}
    onCheck={handleCheck}
  />
))}
```


#####④TodoItem の JSXをBulma のヘルパークラスを用い、完了済みのタスクは文字色を灰色に変化するように編集する。

```
<label className="panel-block">
  <input
    type="checkbox"
    checked={item.done}
    onChange={handleChange}
  />
  <span
    className={classNames({
      'has-text-grey-light': item.done
    })}
  >
    {item.text}
  </span>
</label>
```

#####⑤ {item.text} に CSS クラスを適用させるために <span> で囲った上で、classnames ライブラリを利用する。 

※ IとIIは同じ意味

```
I
className={classNames({
  'has-text-grey-light': item.done // 真偽値
})}
```
```
II
className={item.done ? 'has-text-grey-light' : ''}
```

######⑥TodoItem コンポーネントは完成。
チェックの切り替えと、ON 時の文字色の変化を確認する。

##6.タスクを作成する
#####① 入力欄の値を管理するだけの Input コンポーネントを作成する。

```
function Input() {
  const [text, setText] = React.useState('');

  const handleChange = e => setText(e.target.value);

  return (
    <div className="panel-block">
      <input
        class="input"
        type="text"
        placeholder="Enter to add"
        value={text}
        onChange={handleChange}
      />
    </div>
  );
}
```
#####② エンターキーを押したときのハンドラを実装する。
onKeyDown のハンドラで一旦イベントを受け取り、エンターキーであった時のみ props で受け取る想定の onAdd を実行する。引数には入力されたテキストを渡す。さらに、追加処理の後は入力欄をクリアにする。

```
function Input({ onAdd }) {
  // 中略

  const handleKeyDown = e => {
    if (e.key === 'Enter') {
      onAdd(text);
      setText('');
    }
  };

  return (
    <div className="panel-block">
      <input
        class="input"
        type="text"
        placeholder="Enter to add"
        value={text}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
      />
    </div>
  );
}
```

#####⑤Todo に、追加処理を行うハンドラ関数を実装する。
テキストは子から渡されるので、key と done を追加して、タスクリストに追加する。

```todo
const handleAdd = text => {
  setItems([...items, { key: getKey(), text, done: false }]);
};
```
######⑥.panel-heading の下に Input を配置する。

```todo
<Input onAdd={handleAdd} />
```
##7.フィルタリング機能を作成する
#####①Todo に state を追加する。
フィルタリング条件は、ALL / TODO / DONE の文字列で、今回は表現する。

```
const [filter, setFilter] = React.useState('ALL');
```

######② Filter コンポーネントを実装する。

```
function Filter({ value, onChange }) {
  const handleClick = (key, e) => {
    e.preventDefault();
    onChange(key);
  };

  return (
    <div className="panel-tabs">
      <a
        href="#"
        onClick={handleClick.bind(null, 'ALL')}
      >All</a>
      <a
        href="#"
        onClick={handleClick.bind(null, 'TODO')}
      >ToDo</a>
      <a
        href="#"
        onClick={handleClick.bind(null, 'DONE')}
      >Done</a>
    </div>
  );
}
```

###bind() メソッドとは
bind() メソッドは、呼び出された際に this キーワードに指定された値が設定される新しい関数を生成。この値は新しい関数が呼び出された時、一連の引数の前に置かれる。

#####③Todo に戻り、フィルタリング条件を更新する関数を作成する。

```
const handleFilterChange = value => setFilter(value);
```

#####④items を直接表示するのではなく、条件に応じてフィルタリングされた結果を表示する。
以下のコードを Todo に追加する。

```
const displayItems = items.filter(item => {
  if (filter === 'ALL') return true;
  if (filter === 'TODO') return !item.done;
  if (filter === 'DONE') return item.done;
});

```

#####⑤タスク表示箇所と件数表示箇所を displayItems を使うように編集する。

```
displayItems.map(item => (
  // 中略
))}
<div className="panel-block">
  {displayItems.length} items
</div>
```

#####⑥タブの表示切り替えを実装する。
 classnames を使用する。

```
<a
  href="#"
  onClick={handleClick.bind(null, 'ALL')}
  className={classNames({ 'is-active': value === 'ALL' })}
>All</a>
<a
  href="#"
  onClick={handleClick.bind(null, 'TODO')}
  className={classNames({ 'is-active': value === 'TODO' })}
>ToDo</a>
<a
  href="#"
  onClick={handleClick.bind(null, 'DONE')}
  className={classNames({ 'is-active': value === 'DONE' })}
>Done</a>
```
##7.完成

```
const getKey = () => Math.random().toString(32).substring(2);

function Todo() {
  const [items, setItems] = React.useState([]);
  const [filter, setFilter] = React.useState('ALL');

  const handleAdd = text => {
    setItems([...items, { key: getKey(), text, done: false }]);
  };

  const handleFilterChange = value => setFilter(value);

  const displayItems = items.filter(item => {
    if (filter === 'ALL') return true;
    if (filter === 'TODO') return !item.done;
    if (filter === 'DONE') return item.done;
  });

  const handleCheck = checked => {
    const newItems = items.map(item => {
      if (item.key === checked.key) {
        item.done = !item.done;
      }
      return item;
    });
    setItems(newItems);
  };

  return (
    <div className="panel">
      <div className="panel-heading">
        ⚛️ React ToDo
      </div>
      <Input onAdd={handleAdd} />
      <Filter
        onChange={handleFilterChange}
        value={filter}
      />
      {displayItems.map(item => (
        <TodoItem
          key={item.text}
          item={item}
          onCheck={handleCheck}
         />
      ))}
      <div className="panel-block">
        {displayItems.length} items
      </div>
    </div>
  );
}

function Input({ onAdd }) {
  const [text, setText] = React.useState('');

  const handleChange = e => setText(e.target.value);

  const handleKeyDown = e => {
    if (e.key === 'Enter') {
      onAdd(text);
      setText('');
    }
  };

  return (
    <div className="panel-block">
      <input
        class="input"
        type="text"
        placeholder="Enter to add"
        value={text}
        onChange={handleChange}
        onKeyDown={handleKeyDown}
      />
    </div>
  );
}

function Filter({ value, onChange }) {
  const handleClick = (key, e) => {
    e.preventDefault();
    onChange(key);
  };

  return (
    <div className="panel-tabs">
      <a
        href="#"
        onClick={handleClick.bind(null, 'ALL')}
        className={classNames({ 'is-active': value === 'ALL' })}
      >All</a>
      <a
        href="#"
        onClick={handleClick.bind(null, 'TODO')}
        className={classNames({ 'is-active': value === 'TODO' })}
      >ToDo</a>
      <a
        href="#"
        onClick={handleClick.bind(null, 'DONE')}
        className={classNames({ 'is-active': value === 'DONE' })}
      >Done</a>
    </div>
  );
}

function TodoItem({ item, onCheck }) {
  const handleChange = () => {
    onCheck(item);
  };

  return (
    <label className="panel-block">
      <input
        type="checkbox"
        checked={item.done}
        onChange={handleChange}
      />
      <span
        className={classNames({
          'has-text-grey-light': item.done
        })}
      >
        {item.text}
      </span>
    </label>
  );
}

function App() {
  return (
    <div className="container is-fluid">
      <Todo />
    </div>
  );
}

const root = document.getElementById('root');
ReactDOM.render(<App />, root);
```


##参考サイト
[React入門チュートリアル (5) ToDoアプリを作ってみよう]
(https://www.hypertextcandy.com/react-tutorial-05-wrap-up-with-todo-app)
[CSSフレームワークのススメ - BULMAの導入と覚え書き]
(https://qiita.com/belq/items/10ec41f656e47ee2b540)
[CSS フレームワーク Bulma チートシート]
(https://blog1.mammb.com/entry/2021/06/20/235459)
[Reactのスタイリング(classNameやclassNamesの使い方)]
(https://qiita.com/terry_6518/items/de53123253cf67e048b3)