🖥️

Next.js入門6 Todoアプリ(タスク追加)

に公開

記事一覧

  1. Next.js × Docker 最速環境構築
  2. Next.js入門1 ページ追加
  3. Next.js入門2 コンポーネント
  4. Next.js入門3 無記名関数 & イベントハンドラー
  5. Next.js入門4 Hooks
  6. Next.js入門5 Todoアプリ(タスク一覧)
  7. Next.js入門6 Todoアプリ(タスク追加)
  8. Next.js入門7 Todoアプリ(タスク詳細)
  9. Next.js入門8 Todoアプリ(タスク編集, 削除)

ローカルストレージ

ローカルストレージとは

Webブラウザにデータを永続的に保存できる仕組み

追加

  • 値の保存
// nameという名前で"ケイト"をローカルストレージに保存
localStorage.setItem("name", "ケイト");
  • オブジェクトの保存
const user = { name: "ケイト", age: 18 };
// JSON.stringifyでjsonにして保存する(取得の際にオブジェクトに戻す)
localStorage.setItem("user", JSON.stringify(user));

削除

// userという名前のローカルストレージを削除
localStorage.removeItem("user");

取得

  • 値の取得
// userという名前のローカルストレージを取得
// 存在しない場合はnullになる
const user = localStorage.getItem("user");
  • オブジェクトの取得
const userJson = localStorage.getItem("user");
let user = null;

if (userJson) {
  // JSON.parseでjsonからオブジェクトに変換
  user = JSON.parse(userJson);
}

タスク追加

ローカルストレージを用いてタスクの追加、保存ができるようにする

コンポーネント作成

  1. button.tsxmodal.tsxを作成
.
├── next-app
│   └── components
│       ├── button.tsx # ここに作成
│       └── modal.tsx # ここに作成
├── docker-compose.yml
└── Dockerfile
  1. button.tsxを記述
// 引数の型を定義
type Props = {
  // ボタンが押された時に実行する関数
  onClick: () => void;
  // ボタンの中身
  children: React.ReactNode;
};

export function Button({ onClick, children }: Props) {
  return (
    <button
      type="button"
      className="w-fit border rounded-xl p-3 font-bold"
      // クリック時に渡されたonClick関数を実行する
      onClick={() => onClick()}
    >
      {children}
    </button>
  );
}

  1. modal.tsxを記述
// 引数の型を定義
type Props = {
  // モーダルを閉じる関数
  onClose: () => void;
  // モーダルの中身
  children: React.ReactNode;
};

export function Modal({ onClose, children }: Props) {
  return (
    <div className="fixed top-0 left-0 w-full h-full flex justify-center items-center">
      <div
        className="absolute top-0 left-0 w-full h-full bg-black/[0.5]"
        // 背景をクリック時に渡されたonCloseを実行
        onClick={onClose}
      />

      <div className="relative bg-white rounded-lg p-8 w-1/2 z-10">
        <button
          className="absolute top-5 right-5 text-gray-500 hover:text-black"
          // 閉じるボタンをクリック時に渡されたonCloseを実行
          onClick={onClose}
        >
          閉じる
        </button>
        {children}
      </div>
    </div>
  );
}

ページ編集

  1. task/page.tsxを編集
"use client";

import { useEffect, useState } from "react";
// Buttonコンポーネントをインポート
import { Button } from "@/components/button";
// Modalコンポーネントをインポート
import { Modal } from "@/components/modal";
import { Table } from "@/components/table";
import { Task } from "@/types/task";

function Page() {
  // タスクの初期値を空にする
  const [tasks, setTasks] = useState<Task[]>([]);
  // モーダルの状態を管理する
  const [isOpen, setIsOpen] = useState(false);
  // 新しいタスクを管理する
  const [newTask, setNewTask] = useState<Task>({
    title: "",
    content: "",
  });

  // ローカルストレージからタスクを取得する処理
  useEffect(() => {
    // ローカルストレージからタスクを取得
    const tasks = localStorage.getItem("tasks");
    // タスクがある場合はタスクを設定
    if (tasks) {
      setTasks(JSON.parse(tasks));
    }
  }, []);

  // タスクをローカルストレージに保存する処理
  useEffect(() => {
    if (tasks.length > 0) {
      // タスクがある場合はローカルストレージに保存
      localStorage.setItem("tasks", JSON.stringify(tasks));
    } else {
      // タスクがない場合はローカルストレージから削除
      localStorage.removeItem("tasks");
    }
  }, [tasks]); // tasksが変更されたら実行

  return (
    <div className="flex flex-col gap-8 items-center p-16">
      <h1 className="text-4xl font-bold">タスク一覧</h1>

      {/* モーダルを表示するボタン */}
      <div className="w-full flex justify-end">
        <Button onClick={() => setIsOpen(true)}>タスクを追加</Button>
      </div>

      <Table tasks={tasks}></Table>

      {/* isOpenがtrueの場合のみModalを表示 */}
      {isOpen && (
        // ModalコンポーネントのonCloseにモーダルを閉じる処理を渡す
        <Modal onClose={() => setIsOpen(false)}>
          <form className="flex flex-col gap-4 text-black border-black">
            {/* タスク名入力欄 */}
            <div className="flex flex-col gap-2">
              <label>タスク名</label>
              <input
                className="border p-2 rounded-xl"
                value={newTask.title}
                onChange={(e) =>
                  setNewTask({ ...newTask, title: e.target.value })
                }
              />
            </div>

            {/* タスク内容入力欄 */}
            <div className="flex flex-col gap-2">
              <label>内容</label>
              <textarea
                className="border p-2 rounded-xl"
                value={newTask.content}
                onChange={(e) =>
                  setNewTask({ ...newTask, content: e.target.value })
                }
              />
            </div>

            {/* キャンセルボタンと追加ボタン */}
            <div className="flex justify-end gap-4">
              <Button onClick={() => setIsOpen(false)}>キャンセル</Button>
              <Button
                // 【重要】追加処理
                onClick={() => {
                  setTasks([...tasks, newTask]); // タスクの追加
                  setIsOpen(false); // モーダルを閉じる
                  setNewTask({ title: "", content: "" }); // 新しいタスクを初期化
                }}
              >
                追加
              </Button>
            </div>
          </form>
        </Modal>
      )}
    </div>
  );
}

export default Page;

http://localhost:3000/taskからタスクの追加ができればOK

リロードしても消えないことが確認できるとGood

Discussion