LoginSignup
1
0

More than 1 year has passed since last update.

Reactを使ってToDoリストを作ろう!!

Last updated at Posted at 2022-11-19

講座でReactをToDoリストを作り方を学んだのでまとめています。

まずは、Javascriptを使ってToDoリストを作ろう!!

HTML , CSSの方は省略させていただきます

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>TODO</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div class="input-area">
      <input placeholder="TODOを入力" />
      <button>追加</button>
    </div>
    <div class="incomplete-area">
      <p class="title">未完了のTODO</p>
      <ul>
        <div class="list-row">
          <li>TODOです</li>
          <button>完了</button>
          <button>削除</button>
        </div>
      </ul>
    </div>
    <div class="complete-area">
      <p class="title">完了したTODO</p>
      <ul>
        <div class="list-row">
          <li>TODOでした</li>
          <button>戻す</button>
        </div>
      </ul>
    </div>
    <script src="src/index.js"></script>
  </body>
</html>

style.css

body {
  font-family: sans-serif;
}

input {
  border-radius: 16px;
  border: none;
  padding: 6px 16px;
  outline: none;
}

button {
  border-radius: 16px;
  border: none;
  padding: 4px 16px;
}

button:hover {
  background-color: #ff7fff;
  color: #fff;
  cursor: pointer;
}

li {
  margin-right: 8px;
}

.input-area {
  background-color: #c1ffff;
  width: 400px;
  height: 30px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.incomplete-area {
  background-color: #c6ffe2;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.complete-area {
  background-color: #ffffe0;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.title {
  text-align: center;
  margin-top: 0;
  font-weight: bold;
  color: #666;
}

.list-row {
  display: flex;
  align-items: center;
  padding-bottom: 4px;
}

1.PNG

タスクの追加機能を実装しよう!!

タスクの追加機能 実装方法:
追加ボタンを押した時に入力されたテキストを変数として受け取り、

追加したいのは↓の部分

<div class="list-row">
  <li>TODOです</li>
  <button>完了</button>
  <button>削除</button>
</div> 

divタグのlist-rowクラスの中に

タグ タグが入るような構造にする これをJavascript側で構築すれば良い

追加ボタンを押した時に関数を実行するようにする

index.js

const onClickAdd = () => {
    alert();
}

追加ボタンを押した時に、↑の関数を実行するようにする
=> 追加ボタンの方に目印を付ける
=> 追加ボタンの所にidを付与する

 <!-- add-buttonというidを目印にJavascript側で関数が実行される -->
 <button id="add-button">追加</button>

add-buttonというidを持っている要素に対して、
clickイベントを付与する

document
  .getElementbyId("add-button")
  .addEventListener("click", () => onClickAdd());

追加ボタンをクリックすると、、、
4.PNG

いきなり関数を実行するのではなく、まずは関数が正常に実行されるのかalert()で確認する。

テキストボックスに入力した内容を追加ボタンに押した時、変数に取得するようにする

テキストボックスの要素を取得する
=> テキストボックスに目印を付ける

<input id="add-text" placeholder="TODOを入力" />

変数として受け取りたいので変数を定義する

const onClickAdd = () => {
    const inputText = document.getElementById("add-text").value
};

alert(inputText)でinputTextの中身を確認すると、、、
affa.PNG

追加ボタンを押した時にテキストボックスを初期化する

document.getElementById("add-text").value = ""

div , li タグを実装する

<div class="list-row">
  <li>TODOです</li>
</div>

まずは、この部分から実装する。

一番、外側のdivタグを生成する


//document.createElement("タグ名"): JavascriptでHTMLタグを生成出来る。
const div = document.createElement("div");
console.log(div);
// => <div></div>: 空のdivタグを生成する

クラスを付与する

//divタグにクラスを付与する
div.className = "list-row";
console.log(div);
// => <div class="list-row"></div>

liタグを生成する

const li = document.createElement("li");
console.log(li);
// => <li></li>

liタグの中に入力した文字を設定する

const inputText = document.getElementById("add-text").value;
                    
                    
                    
const li = document.createElement("li");
li.innerText = inputText;
console.log(li);
// => <li>あいうえお</li>;

divタグの下にliタグを設定する

div.appendChild(li);
console.log(div)
// =>  <div class="list-row">
//      <li>あいうえお</li>
//     </div>

divタグをulタグの下に設定する

ulタグに目印を付ける
index.html

 <ul id="incomplete-list">

index.js

 document.getElementById("incomplete-list").appendChild(div);

「あいうえお」と入力して追加ボタンを押すと、、、
2.PNG

ボタンを追加する

//button(完了)タグ作成
const completeButton = document.createElement("button");

//buttonタグの間に完了という文字がある
completeButton.innerText = "完了";
console.log(completeButton);
// => <button>完了</button>

//button(削除)タグ作成
const deleteButton = document.createElement("button");
deleteButton.innerText = "削除";
console.log(deleteButton);
// => <button>削除</button>

divタグの下のliタグの更に、下にbuttonタグが来るようにすれば良い
=>appendChildは、どんどん下に追加されるのでliタグから順番に書けば良い

div.appendChild(completeButton);
div.appendChild(deleteButton);

「あいうえお」と入力して追加ボタンを押すと、、、
fafa.PNG

タスクの削除機能を実装しよう

ボタン生成時にイベントを付与しよう

 //完了ボタン
 const completeButton = document.createElement("button");
 completeButton.innerText = "完了";
 completeButton.addEventListener("click" , () => {
 
 };

 //削除ボタン
 const deleteButton = document.createElement("button");
 deleteButton.innerText = "削除";
 deleteButton.addEventListener("click" , () => {
 
 };

削除の処理を記述する

削除ボタンが押された時、
削除ボタンの親タグ(div)を未完了リストから削除する

親要素であるdivを取得する

//parentNode: 親要素を取得する関数
const deleteTaret = deleteButton.parentNode;
console.log(deleteTaret);
    // => <div class="list-row">
    //      <li>あいうえお</li>
    //      <button>完了</button>
    //      <button>削除</button>
    //    </div>

このdivタグを

    から削除すれば良い
//removeChild: 子要素から指定のものを消す
document.getElementById("incomplete-list").removeChild(deleteTaret);

完了ボタンを実装しよう

完了ボタンを押した時に、未完了のTODOのテキストを
完了したTODOに新しく作る
完了した段階でその行を未完了のTODOから削除する

完了ボタンを押した時に未完了のTODOから削除する

completeButton.addEventListener("click" , () => {
    const deleteTaret = completeButton.parentNode;
    document.getElementById("incomplete-list).removeChild(deleteTaret);
}

ここで、完了/削除ボタンを押した時に未完了のTODOから削除する
=> 同じ機能なので関数を使って、共通化する

//未完了リストから指定の要素を削除する
const deleteFromIncompleteList = () => {
    const deleteTaret = deleteButton.parentNode;
    document.getElementById("incomplete-list").removeChild(deleteTaret);
}

今回、deleteTaretはそれぞれ色んな形で渡ってくる
=> この部分を引数として受け取る

const deleteFromIncompleteList = (target) => {
    document.getElementById("incomplete-list").removeChild(target);
}

削除ボタンの機能をこの関数を使ったものに変更する

deleteButton.addEventListener("click" , () => {
    deleteFromIncompleteList(deleteButton.parentNode);
});

完了ボタンを押した時に削除する機能を関数を使って実装する

completeButton.addEventListener("click" , () => {
    deleteFromIncompleteList(completeButton.parentNode);
});

完了ボタンを押した時に
完了したTODOに追加する機能を実装する

index.htmlを確認すると、、、

<div class="list-row">
  <li>TODOです</li>
  <button>完了</button>
  <button>削除</button>
</div>

(1)完了した親の要素を取得し、

<div class="list-row">
  <li>TODOです</li>
  <button>完了</button>
  <button>削除</button>
</div>

(2)リストのテキストを取得し

 <li>TODOです</li>

(3)中身の部分を空にして、(この部分を無くす)

<li>TODOです</li>
<button>完了</button>
<button>削除</button>

(4)↓の要素をappend.childすれば良い

<li>TODOでした</li>
<button>戻す</button>

完了ボタンを押した所のイベントの要素を削除した上に
完了リストに追加する要素を定義する

(1)

completeButton.addEventListener("click", () => {
    //完了ボタンの親要素を取得する
    const addTarget = completeButton.parentNode;

      //console.log(addTarget);

      // => <div class="list-row">
      //     <li>あいうえお</li>
      //     <button>完了</button>
      //     <button>削除</button>
      //   </div
});

(2)

completeButton.addEventListener("click", () => {
    const addTarget = completeButton.parentNode;
    
    // TODO内容テキストを取得
    const text = addTarget.firstElementChild.innerText;
       // console.log(text);
      // => あいうえお
});

(3)
addTargetで取得しているdivタグのdiv以下を削除し、divタグを使いまわしていく

<div class="list-row">
  <li>TODOです</li>
  <button>完了</button>
  <button>削除</button>
</div>

div以下を初期化する

completeButton.addEventListener("click" , () => {
    const addTarget = completeButton.parentNode;
    const text = addTarget.firstElementChild.innerText;

    //div以下を初期化する
    addTarget.textContent = null;
});

これだとエラーになる! (削除する前に色々やっちゃうと、何を削除すべきかわからなくなる)
error.PNG

解決策:
完了ボタンの一番最初に指定する

completeButton.addEventListener("click" , () => {
    deleteFromIncompleteList(completeButton.parentNode);

    const addTarget = completeButton.parentNode

    const text = addTarget.firstElementChild.innerText

    //div以下を初期化する
    addTarget.textContent = null;
    
    //console.log(addTarget);
    //=> <div class = "list-row"></div>
});

↓にliタグとbuttonタグを設定し、

<div class = "list-row"></div>

このような形を作る

<div class="list-row">
  <li>TODOでした</li>
  <button>戻す</button>
</div>

liタグを生成する

completeButton.addEventListener("click" , () => {
    deleteFromIncompleteList(completeButton.parentNode);
    const addTarget = completeButton.parentNode
    const text = addTarget.firstElementChild.innerText
    addTarget.textContent = null;

    // liタグ生成
    const li = document.createElement("li");
    
    //先程取得したテキストをliタグに入れる
    li.innerText = text;

    // console.log(li);
   // => <li>あいうえお</li>

buttonタグを生成する

completeButton.addEventListener("click" , () => {
    deleteFromIncompleteList(completeButton.parentNode);
    const addTarget = completeButton.parentNode
    const text = addTarget.firstElementChild.innerText
    addTarget.textContent = null;
    const li = document.createElement("li");
    li.innerText = text;

    //buttonタグ生成
    const backButton = document.createElement("button");

    //ボタンの文字を指定する
    backButton.innerText = "戻す";

先程、初期化してdivタグだけになったaddTargetに

, を入れていく
completeButton.addEventListener("click" , () => {
    deleteFromIncompleteList(completeButton.parentNode);
    const addTarget = completeButton.parentNode
    const text = addTarget.firstElementChild.innerText
    addTarget.textContent = null;
    const li = document.createElement("li");
    li.innerText = text;
    const backButton = document.createElement("button");
    backButton.innerText = "戻す";

    //divタグの子要素に各要素を設定する
    //配下を設定する => appendChild
    addTarget.appendChild(li);
    addTarget.appndChild(button);
    
      //console.log(addTarget);
      // =>  <div class="list-row">
      //       <li>あいうえお</li>
      //       <button>戻す</button>
      //     </div>

作成した要素を追加したい場所に目印を付ける

 <ul id="complete-list">

この場所に要素を追加する

completeButton.addEventListener("click" , () => {
    deleteFromIncompleteList(completeButton.parentNode);
    const addTarget = completeButton.parentNode
    const text = addTarget.firstElementChild.innerText
    addTarget.textContent = null;
    const li = document.createElement("li");
    li.innerText = text;
    const backButton = document.createElement("button");
    backButton.innerText = "戻す";
    addTarget.appendChild(li);
    addTarget.appndChild(button);
    
    //完了リストに要素を追加する
    document.getElementById("complete-list").appencChild(addTarget);

タスクを戻す機能を実装する

ボタンにイベントを付与する

//buttonタグ生成
const backButton = document.createElement("button");
backButton.innerText = "戻す";

backButton.addEventListener("click" , () => {
    
});

戻すボタンを押すと、完了したTODOから削除するという機能を実装する

const backButton = document.createElement("button");
backButton.innerText = "戻す";

backButton.addEventListener("click" , () => {
    //押された戻すボタンの親タグ(div)を完了リストから削除
    const deleteTarget = backButton.parentNode;
    
    //どの要素から削除するのか指定する
    document.getElementById("complete-list).removeChild(deleteTarget);
});

未完了のToDoに先程のテキストを設定する

const backButton = document.createElement("button");
backButton.innerText = "戻す";
backButton.addEventListener("click" , () => {
    const deleteTarget = backButton.parentNode;
    document.getElementById("complete-list).removeChild(deleteTarget);

    //テキストを取得する
    const text = backButton.parentNode.firstElementChild.innerText;

    //console.log(text);
   // => あいうえお
});

未完了のリストに追加する
createIncompleteListという関数を作成する

  • テキストボックスに入力したテキストを追加する
  • 戻すボタンを押してテキストを入力する

この2つは、全く同じ事をしている

=> 共通化出来る部分は、関数化する , 条件が異なる部分は引数にする

//未完了リストに追加する関数

//liタグの中身のテキストの部分はtextという引数で指定する
const createIncompleteList = (text) => {

    const div = document.createElement("div");
    div.className = "list-row";
    const li = document.createElement("li");
    
    //テキストを引数で受け取るようにする
    li.innerText = text;

    const completeButton = document.createElement("button");
    completeButton.innerText = "完了";
    completeButton.addEventListener("click", () => {

    deleFromIncompleteList(completeButton.parentNode);
    const addTarget = completeButton.parentNode;
    const text = addTarget.firstElementChild.innerText;
    addTarget.textContent = null;
    const li = document.createElement("li");
    li.innerText = text;
    const backButton = document.createElement("button");
    backButton.innerText = "戻す";

    backButton.addEventListener("click", () => {
    const deleteTaret = backButton.parentNode;
    document.getElementById("complete-list").removeChild(deleteTaret);
    const text = backButton.parentNode.firstElementChild.innerText;
 });

    addTarget.appendChild(li);
    addTarget.appendChild(backButton);
    document.getElementById("complete-list").appendChild(addTarget);
    const deleteTaret = deleteButton.parentNode;
    document.getElementById("incomplete-list").removeChild(deleteTaret);
    deleFromIncompleteList(completeButton.parentNode);
 });

    const deleteButton = document.createElement("button");
    deleteButton.innerText = "削除";

    deleteButton.addEventListener("click", () => {
    const deleteTaret = deleteButton.parentNode;
    document.getElementById("incomplete-list").removeChild(deleteTaret);
    deleFromIncompleteList(deleteButton.parentNode);
});

    div.appendChild(li);
    div.appendChild(completeButton);
    div.appendChild(deleteButton);

    document.getElementById("incomplete-list").appendChild(div);
}

関数を呼び出す側の処理

const onClickAdd = () => {
   const inputText = document.getElementById("add-text").value;
   document.getElementById("add-text").value = "";
    
   //取得したinputTextを関数に入れている
   createIncompleteList(inputText);
};

Reactを使ってTODOアプリを作ろう

JSXで構造を作成し、CSSでスタイリングしよう

HTMLに当たる構造を書く
ただし、HTMLではなく、JSXで書く

HTMLのclassは、JSXでは、classNameとなる

App.jsx

import React from "react";
import "./styles.css";

//HTMLに当たる構造を書く。
//ただし、HTMLではなくJSXで書く

export const App = () => {
  return (
    <>
      {/* HTMLでいうclassは、JSXではclassName */}
      <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>
            <button>削除</button>
          </div>
        </ul>
      </div>
    </>
  );
};

style.css

body {
  font-family: sans-serif;
}

input {
  border-radius: 16px;
  border: none;
  padding: 6px 16px;
  outline: none;
}

button {
  border-radius: 16px;
  border: none;
  padding: 4px 16px;
}

button:hover {
  background-color: #ff7fff;
  color: #fff;
  cursor: pointer;
}

li {
  margin-right: 8px;
}

.input-area {
  background-color: #c1ffff;
  width: 400px;
  height: 30px;
  border-radius: 8px;
  padding: 8px;
  margin: 8px;
}

.incomplete-area {
  background-color: #c6ffe2;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.complete-area {
  background-color: #ffffe0;
  width: 400px;
  min-height: 200px;
  padding: 8px;
  margin: 8px;
  border-radius: 8px;
}

.title {
  text-align: center;
  margin-top: 0;
  font-weight: bold;
  color: #666;
}

.list-row {
  display: flex;
  align-items: center;
  padding-bottom: 4px;
}

Reactでの実装を意識したものに変更する

未完了のTODO , 完了のTODOは、それぞれ、可変の要素より、配列で定義する
それをmapで処理して、

 <div className="list-row">
   <li>ああああ</li>
   <button>完了</button>
   <button>削除</button>
 </div> 
 <div className="list-row">
   <li>うううう</li>
   <button>戻す</button>
 </div>

上記2つの変化する部分を実装する

未完了のTODO , 完了のTODO
それぞれ、可変の要素なので配列で定義する

//未完了のTODOの配列を定義する
const [incompleteTodos, setIncompleteTodos] = useState([
    "ああああ",
    "いいいい"
  ]);
//完了のTODOの配列を定義する
const [completeTodos, setCompleteTodos] = useState(["うううう"]);
  • incompleteTodosという配列を使って、
    未完了のTODOのレンダリングの部分を実装する
  • completeTodosという配列を使って、
    完了のTODOのレンダリングの部分を実装する

incompleteTodosを順番に処理して、
中身としては、↓を記述したい

<div className="list-row">
  <li>ああああ</li>
  <button>完了</button>
  <button>削除</button>
</div> 

completeTodosを順番に処理して、
中身としては、↓を記述したい

 <div className="list-row">
   <li>うううう</li>
   <button>戻す</button>
 </div>

map関数を使おう!!

//未完了のTODOリスト
{incompleteTodos.map((todo) => {
   return (
     <div key={todo} className="list-row">
       <li>{todo}</li>
         <button>完了</button>
         <button>削除</button>
     </div>
    );
})}
//未完了のTODOリスト
{completeTodos.map((todo) => {
   return (
     <div key={todo} className="list-row">
       <li>{todo}</li>
         <button>戻す</button>
     </div>
    );
})}

Reactでループして処理する場合、
ループ内で返却している親のタグに、keyという物を設定する

 <div key={todo} className="list-row">

Reactの裏側で動いている仮想DOMの記述は、
変更前と後で差分のみ抽出し、その差分のみ反映する
=> 何個目の要素か目印を付ける

これが無いとエラーが発生する!!

error.PNG

タスクの追加機能を実装しよう!!

入力した値を先程定義した、incompleteTodosという配列に入れれば良い

入力した値をState化する

export const App = () => {

//これを記述して、State化する
const [todoText, setTodoText] = useState('');

このtodoTextの値は、inputのvalueになるので

<div className="input-area" />
  <input placeholder="TODOを入力" value = {todoText}/>
  <button>追加</button>
</div>

このままだと、キーボードを押しても何も入力されない , 値は変わらない
=>todoTextの初期値は、''より、常に空文字列が設定されている状態

inputで何か変更があれば、todoTextというStateも変更されるようにする
inputの変更が検知されるonChangeを記述する

<input placeholder="TODOを入力" onChange = {}/>

onChangeに設定するStateの値を変更する関数を記述する

event.target.valueに実際に入力した値が入る(←これは、覚えておこう)
それを、setTodoTextの関数に渡していく。

const [completeTodos, setCompleteTodos] = useState(["うううう"]);
  
  //これを記述する
  const onChangeTodoText = (event) => setTodoText(event.target.value)

結果として、todoTextの配列に反映される

const [todoText, setTodoText] = useState('');

流れ:

  • inputの値を取り出す為に入力した値をStateで定義する
  • valueに設定していく
  • onChangeでその入力した値が変わる度にStateも変えたいのでonChangeTodo関数を定義する
  • その関数の中で、onChangeに設定している関数なのでeventの引数を受け取れる
  • 実際に入力した値は、event.target.valueに入っているのでその値をsetTodoTextに渡して、todoTextに反映する

入力した値をStateに保持出来ているか確認しよう!!

const onClickAdd = () => {
    alert(todoText);
}

<button onClick = {onClickAdd}>追加</button>

1.PNG

todoTextの値をincompleteTodosの配列に追加する

追加する為の新しい配列(newTodos)を定義する
配列の結合を使って、今のincompleteTodosにinputに入力した値(todoText)を追加した
新しい配列が生成された

それをsetIncompleteTodos(incompleteTodosを更新する関数)に渡す


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

const onClickAdd = () => {

  //... 配列の結合を使って新しい配列を生成する
  const newTodos = [...incompleteTodos , todoText];
  setIncompleteTodos(newTodos);

}

追加ボタンを押した後も、入力欄に文字が残っている

=> 処理の後にリセットしよう

setTodoTextに空文字を設定する

const onClickAdd = () => {
  const newTodos = [...incompleteTodos , todoText];
  setIncompleteTodos(newTodos);

  //setTodoTextに空文字を設定する
  setTodoText("");

}

TODOに何も入力されないくても追加されてしまう

(画像を貼る)

const onClickAdd = () => {

  //空の場合この3行の処理をしないようにする
  const newTodos = [...incompleteTodos , todoText];
  setIncompleteTodos(newTodos);
  setTodoText("");

}

空文字だったら、処理をreturnする

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

}

タスクの削除機能を実装する

incompleteTodosという配列から対象のものを削除すれば良い

削除ボタンに対してイベントを付与する

<div key = {todo} className="list-row">
  <li>{todo}</li>
  <button>完了</button>
  <button onClick={onClickDelete}>削除</button>
</div>

onClickDeleteという関数を定義する

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

// alertでボタンが押された時に関数が動くか確認する

const onClickDelete = () => {
    alert("削除!")
}

fafkjafjaowe.PNG

<div className="list-row">
  <li>あいうえお</li>
  <button>完了</button>
  <button onClick={onClickDelete}>削除</button>
</div>

<div className="list-row">
  <li>ああああ</li>
  <button>完了</button>
  <button onClick={onClickDelete}>削除</button>
</div>

未完了のTODOは、複数ある
=> どのTODOの削除ボタンが押されたか(何行目の削除ボタンが押されたか)分からない
=> 何行目かという情報を付与する必要がある

onClickDeleteに対して、何行目かという情報を付与する

map: 1つの引数を取ると、実際の値が入る
2つ目の引数(今回は、indexとした)を取るとそこに順番が入る
   1つ目の要素:1 2つ目の要素:2 、、、と入る

<ul>
  {incompleteTodos.map((todo, index) => {
    return(
      <div key = {todo} className = "list-row">
         <li>{todo}
         <button>完了</button>
         <button onClick={onClickDelete(index)}削除</button>
       </div>
     );
   })}
</ul>

1つ目の要素には、1が 2つ目の要素には2が入ってくる。
incompleteTodosの要素が1で2でと判定出来るようになる。
例: 0番目が押されたその配列の0番目の要素 , 1が押されたら1番目の要素を削除する

<button onClick={onClickDelete(index)}削除</button>

このまま()を付けて引数を書くと、この時点で関数が実行されてしまう

試しにalertで確認してみると、、、

const onClickDelete = () => {
  alert("削除!");
};

fafkjafjaowe.PNG

削除ボタンを押してないのにalertが表示される
=> 本来であれば、削除ボタンを押した時に関数が実行されてほしい

その場合、、、
アロー関数を生成して新しく関数を書き直すイメージで修正する

<button onClick={() => onClickDelete(index)}>削除</button>

関数に引数を渡したい時、アロー関数で新しく関数を生成する必要がある!!

onClickDeleteで引数:indexを受け取って、確認してみる

const onClickDelete = (index) => {
    alert(index);
}

vnzxcv.PNG

このindexに基づいて、incompleteTodosから要素を削除すれば良い

indexに基づいて、incompleteTodosから要素を削除する

const onClickDelete = (index) => {

    //現在の、incompleteTodosと全く変わらない新しい配列を作成する
    const newTodos = [...incompleteTodos];

    //新しい配列から指定の要素を削除する
    newTodos.splice(index, 1);

    //このnewTodosでsetIncompleteTodosを更新する
    setIncompleteTodos(newTodos);

};

splice(要素の何番目を受け取るのか , 要素を何個削除するのか)

タスクの完了機能

これまでの要素の追加,削除を組み合わせて実装する

完了ボタンを押した時に発生するイベントを割り当てる

<div key = {todo} className="list-row">
  <li>{todo}</li>
  <button onClick = {() => onClickComplete(index)}完了</button>
  <button onClick = {() => onClickDelete(index)}削除</button>
</div>

関数が正常に動くか確認する

const onClickComplete = (index) => {
    alert(index);
}

vnzxcv.PNG

完了ボタンを押した時に要素が削除されるように実装する

押された行を今の未完了のTODOからコピーした配列から削除し、
新しい完了のTODOは、今の未完了のTODOから押された行の要素を取ってきて追加し
新しい配列を作成する

const onClickComplete = (index) => {

  //現在の、incompleteTodosと全く変わらない新しい配列を作成する
  const newIncompleteTodos = [...incompleteTodos];

  //新しい配列から指定の要素を削除する
  newIncompleteTodos.splice(index, 1);

  //今の完了のTODOのリストの一番後ろに今、押した要素を追加する
  const newCompleteTodos = [...completeTodos, incompleteTodos[index]];

    //Stateを更新する
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
 
};

タスクの戻す機能

完了機能と同じように作れる
押された行を今の完了のTODOからコピーした配列から削除し、
新しい未完了のTODOは今の完了のTODOから押された行の要素を取ってきて追加し
新しい配列を作成する

戻すボタンにイベントを付与する

{completeTodos.map((todo, index) => {
   return
      <div key = {todo} className = "list-row">
        <li>{todo}</li>
        <button onClick={() => onClickBack(index)}>戻す</button>
      </div>
  );
});

onClickBackを記述していく

const onClickBack = (index) => {

  //現在の、completeTodosと全く変わらない新しい配列を作成する
  const newCompleteTodos = [...completeTodos];

  //新しい配列から指定の要素を削除する
  newCompleteTodos.splice(index, 1);

  //今の未完了のTODOのリストの一番後ろに今、押した要素を追加する
  const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];

  //Stateを更新する
  setCompleteTodos(newCompleteTodos);
  setIncompleteTodos(newIncompleteTodos);

}

コンポーネント化しよう

  • テキストボックスとボタンのエリア
  • 未完了のTODOのエリア
  • 完了のTODOのエリア

この3つのコンポ―ネントに分割する

テキストボックスとボタンのエリアをコンポーネント化する

src/components/InputTodo.jsx

import React from 'react';

//コンポーネント名はファイル名と合わせる(他のファイルでも使うのでexportとつける)
export const InputTodo = () => {
  return(
   <div className = "input-area">
     <input
        placeholder = "TODOを入力"
        value = {todoText}
        onChange = {onChangeTodoText}
    />
    <button onClick = {onClickAdd}>追加</button>
   </div>
 );
};

exportしたInputTodoのコンポーネントをApp.jsxで使う

App.jsx

import "./style.css";

//exportしたInputTodoのコンポーネントをimportとする
import {InputTodo}from './components/InputTodo';

                        
                        
                        

return(
    <>
      <InputTodo />

InputTodo.jsxでは、todoText , onChangeTodoText , onClickAdd
がファイル上にはないのでエラーが発生している

error.PNG

=> Propsを用いて、InputTodoの中に渡していく必要がある

コンポーネントの引数に、Propsを定義する

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

親コンポーネントからInputTodoにPropsを渡す

App.jsx


return(
    <>
     <InputTodo
        todoText={todoText}
        onChange={onChangeTodoText}
        onClick={onClickAdd}
      />

InputTodoの方で、渡されたPropsを反映する

export const InputTodo = (props) => {

 //分割代入を使って渡されたpropsを使いやすいように取り出す
 const {todoText , onChange , onClick } = props;

  return(
   <div className = "input-area>
     <input
        placeholder = "TODOを入力"
        value = {todoText}
        onChange = {onChangeTodoText}
    />
    <button onClick = {onClickAdd}追加</button>
   </div>
 );
};

手順の流れ:

  • componentsファルダを作成し、その中にInputTodo.jsxを作成する
  • inputタグ , buttonタグといったものを記述する
  • このコンポーネントに必要なtodoText , onChange , onClickの3つの情報を
    親コンポーネント(App.jsx)からPropsとして渡す

未完了のTODOのコンポーネントを作る

同じ要領で、未完了のTODOのコンポーネントを作成する

components/IncompleteTodo.jsx


import React from "react";

//App.jsxで使いたいのでexportする
export const IncompleteTodos = () => {
   return (
      <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>
              );
            })}
         </ul>
       </div>
  );
};

exportしたIncompleteTodosをApp.jsxにimportする

import { InputTodo } from "./components/InputTodo";

//IncompleteTodosをimportする
import { IncompleteTodos } from "./components/IncompleteTodo";
                            
                            
                            
return (
    <>
       <InputTodo
           todoText = {todoText}
           onChange = {onChangeTodoText}
           onClick = {onClickAdd}
       />
      <IncompleteTodos />

Propsに何が必要か見てみると、、、
incompleteTodos , onClickComplete , onClickDeleteが必要

e.PNG

Propsを渡す

  <IncompleteTodos todos = {incompleteTodos} onClickComplete = {onClickComplete} onClickDelete = {onClickDelete} />

渡されたPropsを使用する

export const IncompleteTodos = (props) => {

  //propsの中身を取り出す
  const { todos , onCickComplete , onClickDelete} = props;

   return (
      <div className = "incomplete-area">
        <p className = "title">未完了のTODO</p>
        <ul>

           //App.jsxは、incompleteTodosをtodosという変数で受け取っている => この部分をtodosにする
           {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>
  );
};

完了のTODOのコンポーネントを作成する

components/CompleteTodos.jsx

import React from "react";

//exportして外部で使えるようにする
export const CompleteTodos = () => {
  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>
  );
};

App.jsx

import { IncompleteTodos } from "./components/InputTodo";

//CompleteTodosをimportする
import { CompleteTodos} from "./components/CompleteTodos";

                            .
                            .
                            .
return (
    <>
      <InputTodo
                            .
                            .
                            .
      <CompleteTodos />

completeTodos(TODOの配列) , onClickBack(戻すボタンの関数)が必要
=> Propsで渡す必要がある

<CompleteTodos todos = {completeTodos} onClickBack = {onClickBack} />

渡されたPropsを使用する

components/CompleteTodos.jsx

export const CompleteTodos = (props) => {
  
  //受け取ったPropsの中身を取り出す
  const {todos , onClickBack} = props;

  return (
    <div className = "complete-area">
       <p className = "title">完了のTODO</p>
       <ul>

          //受け取ったtodosをこちらに設定する
          {todos.map((todo, index) => {

            return (
               <div key = {todo} className = "list-row">
                  <li>{todo}</li>
                  <button onClick = {() => onClickBack
               </div>
            );
           })}
         </ul>
       </div>
 );

CSSをJavascriptで管理する

InputTodo.jsx

 <div className = "input-area>

style.css

.input-area {
   background-color: #c1ffff;
   width: 400px;
   height: 30px;
   border-radius: 8px;
   padding: 8px;
   margin: 8px;
}

CSSで書いている部分をコンポーネントの方に書いて管理する

InputTodo.jsx

const style = {
  backgroundColor: "#c1ffff",
  width: "400px",
  height: "30px",
  borderRadius: "8px",
  padding: "8px",
  margin: "8px"
};
                .
                .
                .

return (
    <div style={style}>

コンポーネント内にスタイルの情報を移すことで
style.cssからinput-areaに対する
関心を取り除く事ができた

CSSも含めて入力欄を変更したい場合、InputTodo.jsxを変更するだけで済むようになった

TODOの上限を設定する

  • コンポーネントを使えなくなる機能
  • メッセージを特定の条件下で出す機能
  • これらの条件を特定の配列の要素数でハンドリングする

これら3つで実装する

実装用に、常に表示されているメッセージを追加する

return (
  <>
    <InputTodo
       todoText = {todoText}
       onChange = {onChangeTodoText}
       onClick = {onClickAdd}
    />
   <p style={{ color: "red" }}>登録できるtodo5個だよ~。消化しろ~。</p>
    <IncompleteTodos
       todos = {incompleteTodos}
       onClickComplete={onClickComplete}
       onClickDelete={onClickDelete}
    />
    <CompleteTodos todos={completeTodos} onClickBack={onClickBack} />
  </>
);

message.PNG

5個入力されている時に、この↑メッセージが表示されるようにする

これ以上、追加出来ないようにする必要があるため
disabledを使って、非活性にする(入力出来なくする)

InputTodo.jsx

<div style = {style}
  <input
        disabled
        placeholder="TODOを入力"
        value={todoText}
        onChange={onChange}
      /> 
  <button disabled onClick={onClick}>
    追加
  </button>
</div>

iewru.PNG

メッセージを5個以上追加されたら表示されるようにする

未完了のTODOが5個以上より、関心がある変数はincompleteTodos
=>この配列が5個以上になったら表示されるようにすればよい

return (
  <>
    <InputTodo
       todoText = {todoText}
       onChange = {onChangeTodoText}
       onClick = {onClickAdd}
    />
    
    //length: 配列の要素数を表す
    {incompleteTodos.length >= 5 && <p style = {{color: 'red'}}>登録できるtodo5個だよ~。消化しろ~。</p>}

inputをdisableするかしないか記述する

inputTodoは、disableにするかしないか意識すれば良い
iewru.PNG

export const InputTodo = (props) => {
    
   //Propsとしてdisabledのフラグを受け取る
   const {todoText , onChange , onClick, disabled } = props;

   return (
      <div style = {style}>
        
        //disabledがtrue => disabledする , false => disabledしない
        <input disabled = {disabled } placheholder = "TDOを入力" value = {todo}
 
        <button disabled = { disabled } onClick={onClick}>追加</button>

      </div>
    );
};

親コンポーネントからinputTodoに
どういった条件の時に、disabledがtrue/falseに
なるのか渡してあげる必要がある

App.jsx

return (
  <>
    <InputTodo
       todoText = {todoText}
       onChange = {onChangeTodoText}
       onClick = {onClickAdd}

       //incompleteTodos.lengthが5以上 => true , 5未満だとfalseになる
       disabled = {incompleteTodos.length >= 5 }

    />
    
    //length: 配列の要素数を表す
    {incompleteTodos.length >= 5 && <p> style = {{color: 'red'}}>登録できるtodo5個だよ~。消化しろ~。</p>}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0