Reactで作るTodoアプリ

前回JavaScriptで作ったTodoアプリをReactで作った時のメモです。

# 完成イメージ

![ezgif.com-gif-maker (5) 15.28.58.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/974719/6e50e91a-2345-e947-11a4-b47f22525d72.gif)

# 実装
## JSX
まずはJSXで基本的なHTML部分を書いときます。

```react:src/App.jsx
import React from "react";
import "./styles.css";

export const App = () => {
  return (
    <>
      <div className="input-area">
        <input placeholder="TODOを入力" />
        <button>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          <div className="list-row">
            <li>ああああ</li>
            <button>完了</button>
            <button>削除</button>
          </div>
          <div className="list-row">
            <li>いいいい</li>
            <button>完了</button>
            <button>削除</button>
          </div>
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          <div className="list-row">
            <li>いいいい</li>
            <button>戻る</button>
          </div>
        </ul>
      </div>
    </>
  );
};
```

## Reactの実装を意識した形にする
Todoの部分をstateにして初期値を設定しておきます。

```react:src/App.jsx
import React from "react";
import { useState } from "react"; //　追加
import "./styles.css";

export const App = () => {
//-----------------------追加-------------------------------------
  const [incompleteTodos, setIncompleteTodos] = useState([
    "ああああ",
    "いいいい"
  ]);

  const [completeTodos, setCompleteTodos] = useState(["うううう"]);
//---------------------------------------------------------------
  return (
    <>
      <div className="input-area">
        <input placeholder="TODOを入力" />
        <button>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
      //-----------------変更------------------------
          {incompleteTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>完了</button>
                <button>削除</button>
              </div>
            );
          })}
     //---------------------------------------------- 
          </div>
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
       //-----------------変更------------------------
          {completeTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>戻る</button>
              </div>
            );
          })}
      //---------------------------------------------- 
          </div>
        </ul>
      </div>
    </>
  );
};
```

### 解説
stateとして定義した`incompleteTodos`と`completeTodos`をmapを使ってループしています。
mapなどを使用するときは`key`の設定を忘れずに！

## Todo追加
```react:src/App.jsx
import React from "react";
import { useState } from "react"; 
import "./styles.css";

export const App = () => {
  const [todoText, setTodoText] = useState(""); //追加
  const [incompleteTodos, setIncompleteTodos] = useState([
    "ああああ",
    "いいいい"
  ]);

  const [completeTodos, setCompleteTodos] = useState(["うううう"]);

  //-----------------------追加--------------------------------
  const onChangeTodoText = (e) => setTodoText(e.target.value);

  const onClickAdd = () => {
    if (todoText == "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };
 //------------------------------------------------------------

  return (
    <>
      <div className="input-area">
    //-----------------変更----------------------
        <input
          placeholder="TODOを入力"
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button onClick={onClickAdd}>追加</button>
    //------------------------------------------
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>完了</button>
                <button>削除</button>
              </div>
            );
          })}
          </div>
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>戻る</button>
              </div>
            );
          })}
          </div>
        </ul>
      </div>
    </>
  );
};
```
### 解説
Reactでinput部分はvalueにstateを持たせinputに`onChange`イベントを使い以下のような関数を書くのは定番。

```react
const onChangeTodoText = (e) => setTodoText(e.target.value);
```
追加ボタンに`onClick`イベントでTodoを追加する関数(`onClickAdd`)を定義してスプレッド構文を使うことで追加している。
`if (todoText == "") return;`この部分は空文字の場合はreturnすることで空文字投稿を防いでいる。

## Todo削除
```react:src/App.jsx
import React from "react";
import { useState } from "react"; 
import "./styles.css";

export const App = () => {
  const [todoText, setTodoText] = useState(""); 
  const [incompleteTodos, setIncompleteTodos] = useState([
    "ああああ",
    "いいいい"
  ]);

  const [completeTodos, setCompleteTodos] = useState(["うううう"]);

  const onChangeTodoText = (e) => setTodoText(e.target.value);

  const onClickAdd = () => {
    if (todoText == "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };

 //---------------追加------------------------
  const onClickDelete = (index) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };
 //------------------------------------------

  return (
    <>
      <div className="input-area">
        <input
          placeholder="TODOを入力"
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo,index) => {  //追加
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>完了</button>
                <button onClick={() => onClickDelete(index)}>削除</button>  //追加
              </div>
            );
          })}
          </div>
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>戻る</button>
              </div>
            );
          })}
          </div>
        </ul>
      </div>
    </>
  );
};
```

### 解説
削除ボタンに`onClick`イベントで`onClickDelete`関数を定義。mapと関数の引数に`index`を渡すことで何番目のTodoの削除ボタンが押されたかどうかを判定できる。(関数に引数を渡すときはアロー関数を使う)
`splice`を使って削除している。

## Todo完了
```react:src/App.jsx
import React from "react";
import { useState } from "react"; 
import "./styles.css";

export const App = () => {
  const [todoText, setTodoText] = useState(""); 
  const [incompleteTodos, setIncompleteTodos] = useState([
    "ああああ",
    "いいいい"
  ]);

  const [completeTodos, setCompleteTodos] = useState(["うううう"]);

  const onChangeTodoText = (e) => setTodoText(e.target.value);

  const onClickAdd = () => {
    if (todoText == "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };

    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };

 //-------------------------追加-----------------------------------------
  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];

    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };
 //--------------------------------------------------------------------

  return (
    <>
      <div className="input-area">
        <input
          placeholder="TODOを入力"
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo,index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickComplete(index)}>完了</button> //追加
                <button onClick={() => onClickDelete(index)}>削除</button> 
              </div>
            );
          })}
          </div>
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>戻る</button>
              </div>
            );
          })}
          </div>
        </ul>
      </div>
    </>
  );
};
```
## Todo戻す
```react:src/App.jsx
import React from "react";
import { useState } from "react"; 
import "./styles.css";

export const App = () => {
  const [todoText, setTodoText] = useState(""); 
  const [incompleteTodos, setIncompleteTodos] = useState([
    "ああああ",
    "いいいい"
  ]);

  const [completeTodos, setCompleteTodos] = useState(["うううう"]);

  const onChangeTodoText = (e) => setTodoText(e.target.value);

  const onClickAdd = () => {
    if (todoText == "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };

    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };

  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];

    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };

 //-------------------------追加-----------------------------------------
  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];

    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };
 //--------------------------------------------------------------------
  return (
    <>
      <div className="input-area">
        <input
          placeholder="TODOを入力"
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          {incompleteTodos.map((todo,index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickComplete(index)}>完了</button> 
                <button onClick={() => onClickDelete(index)}>削除</button> 
              </div>
            );
          })}
          </div>
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo, index) => { //追加
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickBack(index)}>戻る</button> // 追加
              </div>
            );
          })}
          </div>
        </ul>
      </div>
    </>
  );
};
```
### 解説
完了の処理は 未完了リストから削除 → 完了リストに追加
戻るの処理はその逆なので今までの追加と削除の処理を組み合わせることで実装できる。

## コンポーネント分割
最後にコンポーネント分割して完成。

- JSX

```react:src/App.jsx
import React from "react";
import { useState } from "react"; 
import "./styles.css";

export const App = () => {
  const [todoText, setTodoText] = useState(""); 
  const [incompleteTodos, setIncompleteTodos] = useState([
    "ああああ",
    "いいいい"
  ]);

  const [completeTodos, setCompleteTodos] = useState(["うううう"]);

  const onChangeTodoText = (e) => setTodoText(e.target.value);

  const onClickAdd = () => {
    if (todoText == "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };

    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };

  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];

    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };

  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];

    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };
  return (
    <>
    //-----------------------------変更------------------------------------------
     <InputTodo
        todoText={todoText}
        onChange={onChangeTodoText}
        onClick={onClickAdd}
      />
      <IncompleteTodos
        todos={incompleteTodos}
        onClickComplete={onClickComplete}
        onClickDelete={onClickDelete}
      />
      <CompleteTodos completeTodos={completeTodos} onClickBack={onClickBack} />
     //-------------------------------------------------------------------------
    </>
  );
};
```

- 完了リスト

```react:src/components/CompleteTodos.jsx
import React from "react";

export const CompleteTodos = (props) => {
  const { completeTodos, onClickBack } = props;
  return (
    <div className="complete-area">
      <p className="title">完了のTODO</p>
      <ul>
        {completeTodos.map((todo, index) => {
          return (
            <div key={todo} className="list-row">
              <li>{todo}</li>
              <button onClick={() => onClickBack(index)}>戻る</button>
            </div>
          );
        })}
      </ul>
    </div>
  );
};
```

- 未完了リスト

```react:src/components/IncompleteTodos.jsx
import React from "react";

export const IncompleteTodos = (props) => {
  const { todos, onClickComplete, onClickDelete } = props;
  return (
    <div className="incomplete-area">
      <p className="title">未完了のTODO</p>
      <ul>
        {todos.map((todo, index) => {
          return (
            <div key={todo} className="list-row">
              <li>{todo}</li>
              <button onClick={() => onClickComplete(index)}>完了</button>
              <button onClick={() => onClickDelete(index)}>削除</button>
            </div>
          );
        })}
      </ul>
    </div>
  );
};
```
- Input

```react:src/components/InputTodo.jsx 
import React from "react";

export const InputTodo = (props) => {
  const { todoText, onChange, onClick } = props;
  return (
    <div className="input-area">
      <input placeholder="TODOを入力" value={todoText} onChange={onChange} />
      <button onClick={onClick}>追加</button>
    </div>
  );
};
```

# 参考

https://www.udemy.com/course/modern_javascipt_react_beginner/