🦔

Next.js×TypeScriptでTODOアプリを作成する

2023/07/12に公開

はじめに

Next.js×TypeScriptでTODOリストを作成したので、コードを解説していきます。

TODOアプリgif.gif

コード

"use client";
import Form from "../app/components/Form";
import Title from "../app/components/Title";

export default function Home() {
  return (
    <main className="min-h-screen bg-gray-100 py-10 px-4 sm:px-6 lg:px-8">
      <div className="max-w-md mx-auto bg-white rounded-lg shadow-md overflow-hidden">
        <Title/>
        <Form/>
      </div>
    </main>
  )
}
"use client";
import React, {} from 'react';

export default function Title() {
    return (
        <div className="px-4 py-5 sm:px-6 bg-indigo-600">
            <h1 className="text-lg font-semibold text-white">TODOアプリ</h1>
        </div>
    )
}
import React, { useState } from 'react';

interface Todo {
    task: string;
    isCompleted: boolean;
}

interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}

export default function CompleteList({ textList, onDelete }: CompleteListProps) {
    return (
        <ul className="mt-6 space-y-4">
        {textList.map((todo: Todo, index: number) => (
            <li key={index} className="flex items-center justify-between bg-white border border-gray-300 px-4 py-2 rounded-md">
            <span>{todo.task}</span>
            <button className="text-red-600 hover:text-red-800" onClick={() => onDelete(index)}>削除</button>
            </li>
        ))}
        </ul>
    );
}
import React, { useState } from 'react';
import CompleteList from "../components/CompleteLists";

interface Todo {
    task: string;
    isCompleted: boolean;
}

export default function Form() {
    const [todoText, setTodoText] = useState<string>('');
    const [textList, setTextList] = useState<Todo[]>([]);

    const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setTodoText(e.target.value);
    };

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        const newTodo: Todo = {
            task: todoText,
            isCompleted: false
        };
        setTextList(prevTextList => [...prevTextList, newTodo]);
        setTodoText('');
    };

    const handleDelete = (index: number) => {
        setTextList(prevTextList => {
            const updatedList = [...prevTextList];
            updatedList.splice(index, 1);
            return updatedList;
        });
    };

    return (
        <div className="px-4 py-4 sm:p-6">
            <div className="mb-4">
                <form onSubmit={handleSubmit}>
                    <input
                        type="text"
                        className="w-full border border-gray-300 px-3 py-2 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
                        placeholder="タスクを入力してください"
                        value={todoText}
                        onChange={handleInputChange}
                    />
                    <button className="bg-indigo-600 text-white px-4 py-2 rounded-md" type="submit">追加する</button>
                </form>
            </div>
            <CompleteList textList={textList} onDelete={handleDelete}/>
        </div>
    );
}

解説

コードの解説をしていきます

○コンポーネント化について

page.tsxでは、フォームの部分と、タイトルで今回は分けています。

<Title/>
<Form/>

Formコンポーネント内では、テキストを入力するフォーム部分と、登録したテキストを表示する部分でコンポーネント化しています。

//Form.tsx内に書いているコード↓
<CompleteList textList={textList} onDelete={handleDelete}/>

○propsについて

textList={textList}は、CompleteListコンポーネントで作成したテキストを渡して、onDelete={handleDelete}は削除する処理を渡しています。
interfaceで定義しているコードは、propsで値を受け取る側と、渡す側の型を定義しています。

interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}

<CompleteList textList={textList} onDelete={handleDelete}/>

受け取る側では、以下のように記述します。今回はTypeScriptを使用しているので、以下のような受け取り方になっています。

interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}

export default function CompleteList({ textList, onDelete }: CompleteListProps)

メソッドの処理内容について

次は、メソッドの処理内容についてです。
まずはフォーム部分から説明していきます。

 <form onSubmit={handleSubmit}>
  <input
    type="text"
    className="w-full border border-gray-300 px-3 py-2 rounded-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
    placeholder="タスクを入力してください"
    value={todoText}
    onChange={handleInputChange}
  />

このコードでは、form要素とinput要素にそれぞれ作成したメソッドを割り当てています。
それぞれのメソッドについて説明します。関係する箇所を抜粋しながら説明します。

interface Todo {
    task: string;
    isCompleted: boolean;
}

const [textList, setTextList] = useState<Todo[]>([]);

const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        const newTodo: Todo = {
            task: todoText,
            isCompleted: false
        };
        setTextList(prevTextList => [...prevTextList, newTodo]);
        setTodoText('');
};

このコードは、登録ボタンを押した時に、フォームの内容を登録する処理に関係する処理です。
interfaceでフォームを送信する時に使用する変数に型を指定し、handleSubmit()内で、送られてきた内容を各変数に登録し。useState内で指定した、setTextList()を呼び出して、保存します。
最後のsetTodoText('')でボタンを押した後に、フォームの中身を空にする処理を行います。

interface Todo {
    task: string;
    isCompleted: boolean;
}

上のコードは、propsで渡す側と受け取る側のコンポーネントファイルで、受け取るデータの型を定義します。

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        setTodoText(e.target.value);
    };

上のコードは、input要素に入力がされるたびに、画面が再レンダリングして、setTodoTextを動かして、useStateで定義した変数(todoText)に入力内容を格納するコードです。

○表示部分

次は登録したタスクを表示する部分のコードになります。
以下がコードです。

interface Todo {
    task: string;
    isCompleted: boolean;
}

interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}

export default function CompleteList({ textList, onDelete }: CompleteListProps) {
    return (
       <ul className="mt-6 space-y-4">
            {textList.map((todo: Todo, index: number) => (
                <li key={index} className="flex items-center justify-between bg-white border border-gray-300 px-4 py-2 rounded-md">
                    <span>{todo.task}</span>
                    <button className="text-red-600 hover:text-red-800" onClick={() => onDelete(index)}>削除</button>
                </li>
            ))}
        </ul>

interfaceで型を定義

表示するテキストの型を定義して、

interface Todo {
    task: string;
    isCompleted: boolean;
}

Form.tsxから受け取ったpropsの値に対しての型付けをしています。

interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}

次は表示部分です。

<ul className="mt-6 space-y-4">
    {textList.map((todo: Todo, index: number) => (
      <li key={index} className="flex items-center justify-between bg-white border border-gray-300 px-4 py-2 rounded-md">
        <span>{todo.task}</span>
        <button className="text-red-600 hover:text-red-800" onClick={() => onDelete(index)}>削除</button>
      </li>
    ))}
 </ul>

propsで受け取った変数textListをmap関数を用いて、一覧で表示しています。
textList.map((todo: Todo, index: number) => ...) は、textList 配列の各要素に対して関数を適用し、新しい配列を生成します。各要素は todo という名前の変数に割り当てられます。また、index は現在の要素のインデックスです。
つまり、受け取ったデータの塊を「todo」に格納して、その塊ごとに「index」という変数でインデックスを割り当てて、1つずつ表示できるようにしています。

削除機能

最後に削除機能を実装しているコードです。
削除に関係しているコードを抜粋して解説していきます。
まずは以下のコードです。
メソッドを作成して、propsで渡しています。
ボタンを押したときに、その要素のindexを受け取り、そのindexに一致する要素を排除した配列をreturnする処理を書いています。

const handleDelete = (index: number) => {
        setTextList(prevTextList => {
            const updatedList = [...prevTextList];
            updatedList.splice(index, 1);
            return updatedList;
        });
    };
<CompleteList textList={textList} onDelete={handleDelete}/>

受け取ったメソッドを受け取り、interfaceで型を定義しています。
メソッドをボタンに割り当てて、ボタンが押されたときに実行されるように定義しています。

interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}
<button className="text-red-600 hover:text-red-800" onClick={() => onDelete(index)}>削除</button>

Discussion