1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ReactでToDoアプリをつくってみよう

Posted at

1. はじめに

今回は、Reactを使ってシンプルなToDoアプリを作ってみます!
「状態管理(useState)」や「イベント処理」「リストの描画(map)」など、Reactの超基本がギュッと詰まった内容なので、初心者の方にピッタリです。

※もしカウンターアプリを作ったことがある方は、そこから少しステップアップした内容になります!

2. プロジェクトの準備

まずは、Reactのプロジェクトを作成しましょう。

ターミナル(コマンドプロンプト)を開いて、作業したいフォルダ(例:デスクトップなど)で以下のコマンドを実行します。

npm create vite@latest

プロジェクト名の入力を求められたら、todo-app-react と入力してエンターキーを押してください。(プロジェクト名は自由に決めてもok)

その後、以下のコマンドでプロジェクトのフォルダに移動して、必要な依存をインストールします。

cd todo-app-react
npm install
npm run dev

ブラウザで http://localhost:5173 を開いて、「Vite + React」と表示されていれば準備OKです!

スクリーンショット 2025-04-17 17.20.36.png

CSSの準備

デフォルトで用意されているCSS(App.cssindex.css)は今回使わないので、中身をすべて削除しておきましょう。

App.jsxの初期化

次に、src/App.jsx を開いて、以下のように中身を変更してください!

App.jsx
const App = () => {
  return <div>App</div>;
};

export default App;

これで、Reactのアプリの土台が完成しました!

スクリーンショット 2025-04-17 17.21.55.png

3. UIを作る

準備ができたら、いよいよアプリを作っていきましょう!
まずはUI(見た目の部分)から取りかかります。

App.jsx を以下のように書き換えてください👇
ここでは特に新しいReactの要素は出てこないので、サクッと進めちゃいます!

App.jsx
import "./App.css";

const App = () => {
  return (
    <div>
      <header>
        <h1>ToDoアプリ</h1>
      </header>

      <main>
        <div className="list">
          <div className="list-header">
            <p className="list-title">大学</p>
          </div>
          <div className="cards-container">
            <div className="card">
              <div className="todo">これ</div>
              <div className="delete"></div>
            </div>
            <div className="card">
              <div className="todo">それ</div>
              <div className="delete"></div>
            </div>
            <div className="card">
              <div className="todo">あれ</div>
              <div className="delete"></div>
            </div>
          </div>
          <div className="list-footer">
            <input
              type="text"
              className="add-todo"
              placeholder="タスクを追加"
            />
            <button className="add-button">追加</button>
          </div>
        </div>
      </main>
    </div>
  );
};

export default App;

HTML部分がちょっと長めですが、今のところはただの見た目なので気楽に!

ここで一つポイント💡
Reactでは、HTMLのクラス指定に class は使えません。
代わりに className と書く必要があります。これはJSXのルールです!

続いて、CSSも当てて見た目を整えましょう!
今回はReactに集中したいので、CSSの解説は省略します。

App.css に以下のスタイルをコピペしてください👇

App.css
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

header {
  height: 64px;
  display: flex;
  align-items: center;
  background-color: #f8fafd;
  display: flex;
  justify-content: space-between;
}

header h1 {
  font-size: 20px;
  padding-left: 24px;
}

.list-creation {
  width: 20vw;
  height: 40px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: end;
  padding: 0px 24px;
  gap: 8px;
}

.list-creation h2 {
  font-size: 16px;
  margin-right: 8px;
}

.list-creation input {
  width: 180px;
  height: 32px;
  padding: 4px;
  border-radius: 4px;
  border: 1px solid #ccc;
}

.list-creation button {
  width: 72px;
  height: 32px;
  margin-left: 5px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

main {
  background-color: #f8fafd;
  display: flex;
  flex-wrap: nowrap;
  align-items: start;
  height: calc(100vh - 64px);
  padding: 0 12px 12px 12px;
  overflow-x: auto;
}
.list {
  background-color: white;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
  min-width: 250px;
  width: 250px;
  margin: 0px 8px;
  padding: 8px;
  border-radius: 4px;
}

.list-header {
  height: 40px;
  padding: 8px 0px 0px 4px;
  font-weight: bold;
  font-size: 0.9em;
}
.cards-container {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 8px;
}
.card {
  background-color: white;
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
  padding: 6px;
  border-radius: 4px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.card .delete {
  z-index: 10;
  color: red;
  font-size: 0.8em;
  margin-right: 8px;
}
.card :hover {
  cursor: pointer;
}
.card:hover .delete:after {
  content: "削除";
}
.list-footer {
  height: 40px;
  padding: 8px 0px 0px 4px;
  font-weight: bold;
  font-size: 0.9em;
}
.list-footer input {
  width: 70%;
  height: 32px;
  padding: 4px;
  border-radius: 4px;
  border: 1px solid #ccc;
}
.list-footer button {
  width: 25%;
  height: 32px;
  margin-left: 5px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

ブラウザで表示してみると、かなりToDoアプリっぽくなってるはず!
デザインはGoogleカレンダーのタスクっぽいイメージで作ってみました。

スクリーンショット 2025-04-17 17.29.16.png

これでUI部分はひとまず完成です!
次は、実際にタスクを追加・削除できるように機能をつけていきましょう!

4. 機能を実装する

ここからはいよいよ「動く」ToDoアプリを作っていきます!
前回のカウンターアプリで出てきた、あの2つの主役も大活躍します。

  • ボタンを押したときのイベント → onClick
  • 状態を管理する変数 → useState

今回もこれらをガンガン使っていくので、復習しながらやっていきましょー!

4-1. タスクを状態変数で管理しよう

まずは、表示するタスクを状態変数として持たせて、あとで追加・削除できるようにします。
前回の復習通り、useStateを使ってこんな感じで書きます👇

App.jsx
+ import { useState } from "react";
import "./App.css";

const App = () => {
+ const [cards, setCards] = useState(["タスク1", "タスク2", "タスク3"]); // タスクを管理するための状態変数
// タスクを管理するための状態変数

  return (
    // 省略
  );
};

export default App;

ここではcardsって名前にして、配列でタスクを持っています。
次はこの配列を画面に表示させていきます!

4-2. map関数で配列を展開しよう

配列の内容を画面に表示させるには、中身を1つ1つ取り出して並べる必要があります。このように、配列を展開する時に使うのが今回の新要素、map関数です!

map関数は、配列名.map()の形で使います。

<div className="cards-container">
    {cards.map((card,) => (
      <div className="card">
        <p>{card}</p>
        <div className="delete"></div>
      </div>
    ))}
</div>

「かっこ多くてムズっ😇」ってなるかもしれないので、ステップ分解して解説します👇

map関数を書く時に考えてること

①HTMLの中にJavaScript書きたいときは {} で囲う

<div className="cards-container">
    {cards.map()}
</div>

map()「アロー関数(()=>)」を使って処理を書く
一行で書くなら {} じゃなくて () を使うのがポイント!

<div className="cards-container">
    {cards.map(()=>())}
</div>

③配列の中身1個ずつ取り出す「引数」を書く(例:card)
 配列名を複数形にして、その単数形を入れるのが普通です!

<div className="cards-container">
    {cards.map((card)=>())}
</div>

④表示したい要素を書く(今回はdivとかpタグ)

<div className="cards-container">
    {cards.map((card) => (
      <div className="card">
        <p className="card-text">{card}</p>
        <div className="delete-button"></div>
      </div>
    ))}
</div>

⑤完成!

ここまで来たら、状態変数の中身がちゃんと表示されてるのが確認できるはず!

スクショみたいに、3つのタスクが出ていればOK✨

スクリーンショット 2025-04-17 19.35.46.png

4-3. タスクを追加できるようにしよう

次は、「タスクを追加する機能」を作っていきます!
input に文字を入力して「追加」ボタンを押すと、新しいタスクがリストに追加される仕組みを作ります。

入力された内容をuseStateで管理しよう!

まずは、フォームの中に入力された文字を状態変数で管理する必要があります。
新しく inputValue という状態を追加しましょう。

App.jsx
import { useState } from "react";
import "./App.css";

const App = () => {
  const [cards, setCards] = useState(["タスク1", "タスク2", "タスク3"]); // タスクを管理するための状態変数
+ const [inputValue, setInputValue] = useState(""); // 入力値を管理するための状態変数

  return (
    // 省略
  );
};

export default App;

inputに状態変数を紐づけよう!

フォーム系の要素(input, textarea など)は、
「今入力されてる文字」を常に状態に反映させる必要があります。

<input
  type="text"
  className="add-todo"
  placeholder="タスクを追加"
  value={inputValue} // 入力値を表示
  onChange={(e) => setInputValue(e.target.value)} // 入力値を更新
/>

これをApp.jsxにも組み込んであげましょう!

App.jsx
import { useState } from "react";
import "./App.css";

const App = () => {
  // 省略

  return (
    <div>
      {/* 省略  */}
          <div className="list-footer">
            <input
              type="text"
              className="add-todo"
              placeholder="タスクを追加"
+             value={inputValue} // 入力値を表示
+            onChange={(e) => setInputValue(e.target.value)} // 入力値を更新
            />
            <button className="add-button">追加</button>
          </div>
        </div>
      </main>
    </div>
  );
};

export default App;

追加ボタンをクリックしたときの処理を作ろう!

次は「追加」ボタンを押したときの動きを作っていきます。
onClick イベントで、新しいタスクを今の cards に追加する関数を呼び出します。

App.jsx
import { useState } from "react";
import "./App.css";

const App = () => {
  const [cards, setCards] = useState(["タスク1", "タスク2", "タスク3"]); // タスクを管理するための状態変数
  const [inputValue, setInputValue] = useState(""); // 入力値を管理するための状態変数

+ // タスクを追加する関数
+ const handleAddTask = () => {
+   if (!inputValue) return; // 入力値が空の場合は何もしない
+
+   const newCards = [...cards, inputValue]; // 現在のタスクに新しいタスクを追加
+   setCards(newCards); // cardsを更新
+   setInputValue(""); // 入力フィールドを空にする
+ };

  return (
    <div>
      <header>
        <h1>ToDoアプリ</h1>
      </header>

      <main>
        <div className="list">
          {/* 省略  */}
          <div className="list-footer">
            <input
              type="text"
              className="add-todo"
              placeholder="タスクを追加"
              value={inputValue} // 入力値を表示
              onChange={(e) => setInputValue(e.target.value)} // 入力値を更新
            />
-           <button className="add-button">
+           <button className="add-button" onClick={handleAddTask}>
              追加
            </button>
          </div>
        </div>
      </main>
    </div>
  );
};

export default App;
「...cards」ってなに? → スプレッド構文!
  [...cards, inputValue]

これは cards の中身をまるっと展開して、最後に inputValue をくっつけた配列を新しく作ってるイメージ。
実際にこう書くのと同じ意味です👇

 ["タスク1", "タスク2", "タスク3", inputValue]

でも毎回「タスク1〜3」と書くのはしんどいので、スプレッド構文(...)を使うと便利&スッキリ!

これで、inputに文字を入れて「追加」ボタンを押すと、ちゃんとタスクが追加されるようになりました!

Videotogif.gif

4-4. filter関数を使って、タスクを削除できるようにしよう

完了したタスク、いつまでも残ってたらスッキリしないですよね。ということで、今回は「タスク削除機能」をサクッと実装して

流れは追加ボタンとほぼ同じ。タスクを削除する関数を作って、削除ボタンにonClickで登録します。ただ、削除するときのcardsの更新で、新要素フィルター関数を使います!

App.jsx
import { useState } from "react";
import "./App.css";

const App = () => {
  const [cards, setCards] = useState(["タスク1", "タスク2", "タスク3"]); // タスクを管理するための状態変数
  const [inputValue, setInputValue] = useState(""); // 入力値を管理するための状態変数

  // タスクを追加する関数
  const handleAddTask = () => {
    if (!inputValue) return; // 入力値が空の場合は何もしない

    const newCards = [...cards, inputValue]; // 現在のタスクに新しいタスクを追加
    setCards(newCards); // cardsを更新
    setInputValue(""); // 入力フィールドを空にする
  };

+ // タスクを削除する関数
+ const handleDeleteTask = (tempCard) => {
+   const newCards = cards.filter((card) => card !== tempCard); // 指定されたタスクを除外
+   setCards(newCards); // cardsを更新
+ };

  return (
    <div>
      <header>
        <h1>ToDoアプリ</h1>
      </header>

      <main>
        <div className="list">
          {/* 省略  */}
          <div className="cards-container">
            {cards.map((card) => (
              <div className="card">
                <p className="card-text">{card}</p>
                <div
                  className="delete"
+                 onClick={() => handleDeleteTask(card)}
                ></div>
              </div>
            ))}
          </div>
          {/* 省略  */}
        </div>
      </main>
    </div>
  );
};

export default App;

ここで新たに登場したfilter関数は、「条件に一致するものだけを残す」関数です!

const 新しい配列 = 元の配列.filter((要素) => 条件);

今回はこう書きました。

const newCards = cards.filter((card) => card !== tempCard);

例えば、cards がこうだったとき:

["タスク1", "タスク2", "タスク3"]

タスク2 を消したい場合は:

cards.filter((card) => card !== "タスク2")

これで確認すると、きちんとタスクを削除できるはずです!

Videotogif (1).gif

4. おわりに

これでToDoアプリは一旦完成です!今回は新要素が3つ登場しました!

  • map関数
  • スプレッド構文
  • filter関数

今後、Reactで開発をする上でどれもよく使うものなので、しっかり復習しておいてください!

次回は、このToDoアプリをさらにグレードアップさせていきたいと思いますのでお楽しみに!

1
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?