【React】Todoリスト作ってみた

## はじめに
メモ替わりとしての投稿なのでちょっと雑かも笑

## 完成系
![todo-image_out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3886720/f0332aed-23e5-9d20-a65d-a6cb970b9725.gif)

今回はあくまでCSSを学習するためではないので殆どいじっていないです。（見た目はダサいですが…）
暇な時があればCSSを変更しようかなと思います笑

## 機能
1. タスク追加
2. タスクの状態選択
3. タスク削除

機能は至ってシンプルで上の3つを動作させようと思います。

## 1. Reactのプロジェクトを作成

```:ターミナル
npx create-react-app 'todo-app'
```
```:ターミナル(サーバー起動)
npm start 
```
ディレクトリ構造は今回はこんな感じにしました↓
```
- node_modules
- src
   |- components
       |- components
           |- TodoList.jsx
   |- App.jsx
   |- index.js
-package-lock.json
-package-json 
```
src直下にcomponentsディレクトリを作成して、その中にTodoList.jsxファイルを入れました。App.jsxだけで完結させようかとも思いましたが、propsの渡し方等も学ぼうと思ったのでTodo追加部分をTodoList.jsxにまとめました。

今回使うのはTodoList.jsxとApp.jsxのみです！それではいきましょ〜ー！！

## 2. Todoリストの機能追加前の準備

### 1. Todoリスト雛形作る

```react:App.jsx
import './App.css';
import TodoList from './components/TodoList'

function App() {

  return (
    <>
      <h1>Todoリスト</h1>
      <p>残りのタスク：０</p>
      <input />
      <button>追加</button>
      <button>削除</button>
      <TodoList />
    </>
  )
}

export default App;
```
```react:TodoList.jsx
import React from 'react';

const TodoList = () => {
  return (
    <div>
      <label>
        <input 
          type='checkbox'
        />
        todo1
      </label>
    </div>
  )
}

export default TodoList;
```

App.jsxでTodoListをインポートして<TodoList />はTodoList.jsxのconst TodoList = ...(以下省略)を呼び出しています。

### 2. タスクの状態を管理する

```react:App.jsx
//...省略
import {useState} from 'react';

function App() {
  const [todos, setTodos] = useState([
    {id: 1, name: 'todo1', completed: false}
  ])
//...省略
```

ReactフックのuseStateを使ってタスクの状態を管理します。{id: 1, name: 'todo1', completed: false}これは初期値です。completed: falseについてはTodoの完了か未完了かの状態を表しています。←後で使います。値を更新する際は、setTodosを使います。

### 3. 初期値を表示してみる

```react:App.jsx
//...省略
    <button>削除</button>
    <TodoList todos={todos} />
  </>
//...省略
```
```react:TodoList.jsx
//...省略
const TodoList = ({ todos }) => {
  return (
    todos.map(todo => {
      return (
        <div key={todo.id}>
          <label>
            <input
              type="checkbox"
            />
            {todo.name}
          </label>
        </div>
      )
    })
  )
}
//...省略
```

App.jsxでTodoList.jsxにpropsを渡します。TodoList.jsxで渡されたtodosをmapメソッドを使って1つずつ取り出してtodo.nameを記載して初期値を表示させます。また、keyを入れないとwarningが出るので入れます。
こうなったらok↓
![スクリーンショット 2024-09-16 22.33.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3886720/f19b5038-0662-51d5-4984-c897183658bf.png)

これで準備が整ったので次はTodoの機能を作っていきましょう！

## 3. タスクの追加

### 1. 入力した値を取得出来るか確認する

```App.jsx
  //...省略
  const [todos, setTodos] = useState([
    {id: 1, name: 'todo1', completed: false}
  ])
　//追加
  const [inputValue, setInputValue] = useState('')

  const handleInputValue = (e) => {
    setInputValue(e.target.value)
  }
  const handleAddTodo = () => {
    console.log(inputValue)
  }
  
  return (
  　<>
      <h1>Todoリスト</h1>
      <p>残りのタスク：０</p>
      //追加
      <input value={inputValue} onChange={handleInputValue} />
      <button onClick={handleAddTodo}>追加</button>
  //...省略
  ```
  inputに適当な文字を入力して追加ボタンをクリックした時に値がconsoleに出力されればok!
  
  ![スクリーンショット 2024-09-17 22.33.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3886720/3c247fde-904e-b0ce-78bd-5fe0a8bae3fb.png)

### 2. 入力したタスクを追加する

値が取得出来たかどうかを確認できたら、タスク追加機能を作成する。

```App.jsx
const handleInputValue = (e) => {
    setInputValue(e.target.value)
  }
  //追加
  const handleAddTodo = () => {
    if(inputValue === '') return
    setTodos((prevTodos) => {
      return [...prevTodos, {id: uuidv4(), name: inputValue, completed: false}]
    })
    setInputValue('')
  }
  //ここまで
  ```

  先ほど値を取得する際に書いたコードを値を追加出来るように書き換えます。

#### handleAddTodoの部分について

  **if(inputValue === '') return**
これは値が空の場合はtodoが追加出来ないようになっています。

**setTodos((prevTodos) => {
      return [...prevTodos, {id: uuidv4(), name: inputValue, completed: false}]
    })**
**...prevTodosの部分：** 既存のtodosの全要素を展開して、新しい配列にコピーしています。
**{id: uuidv4(), name: inputValue, completed: false}の部分：** 新しいTodoオブジェクトを作成しています。
イメージ的には{id: uuidv4(), name: inputValue, completed: false}が作成されて...prevTodosに追加されていくっていう感じ？かなぁ？言葉にするのって難しい。。。

***id: uuidv4()の部分：*** これはuuidを使っており、重複していないidを生成してくれます。詳しい使い方はこちら→https://www.npmjs.com/package/uuid
これでタスク追加機能は出来ました。

ここまでのソースコードはこちら↓

```App.jsx
import './App.css';
import TodoList from './components/TodoList';
import {useState} from 'react';
import { v4 as uuidv4 } from 'uuid';

function App() {
  const [todos, setTodos] = useState([
    {id: 1, name: 'todo1', completed: false}
  ])

  const [inputValue, setInputValue] = useState('')

  const handleInputValue = (e) => {
    setInputValue(e.target.value)
  }
  const handleAddTodo = () => {
    if(inputValue === '') return
    setTodos((prevTodos) => {
      return [...prevTodos, {id: uuidv4(), name: inputValue, completed: false}]
    })
    setInputValue('')
  }
  return (
    <>
      <h1>Todoリスト</h1>
      <p>残りのタスク：０</p>
      <input value={inputValue} onChange={handleInputValue} />
      <button onClick={handleAddTodo}>追加</button>
      <button>削除</button>
      <TodoList todos={todos} />
    </>
  )
}

export default App;
```

```TodoList.jsx
import React from 'react';

const TodoList = ({ todos }) => {
  return (
    todos.map(todo => {
      return (
        <div key={todo.id}>
          <label>
            <input
              type='checkbox'
            />
            {todo.name}
          </label>
        </div>
      )
    })
  )
}

export default TodoList;
```

## 3. タスクの状態選択機能作成

```App.jsx
//...省略
    setInputValue('')
  }

 //追加
  const todoCompleted = (id) => {
    const newTodos = [...todos]
    const checkedTodo = newTodos.find((newTodo) => newTodo.id === id)
    checkedTodo.completed = !checkedTodo.completed
    setTodos(newTodos)
  }
 //ここまで
  
  return (
//...省略

export default App;
```

```TodoList.jsx
import React from 'react';

const TodoList = ({ todos, todoCompleted }) => {
  return (
    todos.map(todo => {
      //追加
      const handleTodoClick = () => {
        todoCompleted(todo.id)
      }
      //ここまで
      return (
        <div key={todo.id}>
          <label>
            <input
              type='checkbox'
              //追加
              checked={todo.completed}
              onChange={handleTodoClick}
              //ここまで
            />
            {todo.name}
          </label>
        </div>
      )
    })
  )
}

export default TodoList;
```

#### App.js　const todoCompleted = ...(以下省略)について

**const newTodos = [...todos]**
...todosをコピーしています。
何でコピーするのか、以下参考に↓
https://ja.react.dev/learn/updating-arrays-in-state

**const checkedTodo = newTodos.find((newTodo) => newTodo.id === id)**
コピーしたtodoとチェックしたtodoが一致したものをfindメソッドで探します。findメソッドは条件に一致した最初の値を取得することができます。

**checkedTodo.completed = !checkedTodo.completed**
findメソッドで取得したcheckedTodo.completedを反転させることによってチェックボックスのクリックを実現させています。

**setTodos(newTodos)**
この記述によりチェックボックスの状態を更新できます。

# 4. タスク削除

```App.jsx
  //...省略
   setTodos(newTodos)
  }
  
  //追加
  const handleClear = () => {
    setTodos(todos.filter(todo => !todo.completed))
  }
  //ここまで
  
  return (
  //...省略
  ```

  ここではfilterメソッドを使っています。filterメソッドは条件に一致したもの、つまりtrueを返す要素のみを新しい配列に残します。
  
  ① todo.completedがtrueの場合（**チェックあり**）：**!todo.completedはfalse**
  ② todo.completedがfalseの場合（**チェックなし**）：**!todo.completedはtrue**

  filterメソッドはtrueを返す要素のみを残すので①は消えて②は残ります。つまり、**チェックがついたものを削除できる** ということです！
  ここの理解がムズかった、、、

# 5. 残りのタスクの数を表示する

```App.jsx
//...省略
return (
    <>
      <h1>Todoリスト</h1>
      //追加
      <p>残りのタスク：{todos.filter(todo => !todo.completed).length}</p>
      //ここまで
      <input value={inputValue} onChange={handleInputValue} />
      //...省略
```
**残りのタスク：{todos.filter(todo => !todo.completed).length}**
filterでtodo.completedの状態を反転させることによって残タスクを表示することができます。

これで基本機能だけですが、todoリストを完成させることができました！
全ソースコードはこちら↓
```diff_javascript:App.jsx
import './App.css';
import TodoList from './components/TodoList';
import {useState} from 'react';
import { v4 as uuidv4 } from 'uuid';

function App() {
  const [todos, setTodos] = useState([
-   {id: 1, name: 'todo1', completed: false}//これ消していいです。
  ])

  const [inputValue, setInputValue] = useState('')

  const handleInputValue = (e) => {
    setInputValue(e.target.value)
  }
  const handleAddTodo = () => {
    if(inputValue === '') return
    setTodos((prevTodos) => {
      return [...prevTodos, {id: uuidv4(), name: inputValue, completed: false}]
    })
    setInputValue('')
  }

  const todoCompleted = (id) => {
    const newTodos = [...todos]
    const checkedTodo = newTodos.find((newTodo) => newTodo.id === id)
    checkedTodo.completed = !checkedTodo.completed
    setTodos(newTodos)
  }

  const handleClear = () => {
    setTodos(todos.filter(todo => !todo.completed))
  }
  return (
    <>
      <h1>Todoリスト</h1>
      <p>残りのタスク：{todos.filter(todo => !todo.completed).length}</p>
      <input value={inputValue} onChange={handleInputValue} />
      <button onClick={handleAddTodo}>追加</button>
      <button onClick={handleClear}>削除</button>
      <TodoList todos={todos} todoCompleted={todoCompleted} />
    </>
  )
}

export default App;
```
```TodoList.jsx
import React from 'react';

const TodoList = ({ todos, todoCompleted }) => {
  return (
    todos.map(todo => {
      const handleTodoClick = () => {
        todoCompleted(todo.id)
      }
      return (
        <div key={todo.id}>
          <label>
            <input
              type='checkbox'
              checked={todo.completed}
              onChange={handleTodoClick}
            />
            {todo.name}
          </label>
        </div>
      )
    })
  )
}

export default TodoList;
```
一応githubにも載せてます→https://github.com/kurumi-program/todo-app

# 最後に
初めてのQiitaでの記事投稿でしたが、コードを言語化して説明文にするのがやっぱり難しかったです。
ですが、改めていい復習になったのでこれからも作成を続けていこうと思います。ほぼ自己満ですが笑