LoginSignup
6
3

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

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

Last updated at Posted at 2023-06-25

はじめに

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

TODOアプリgif.gif

コード

page.tsx
"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>
  )
}
/Components/Title.tsx
"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>
    )
}
/Components/CompleteList.tsx
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>
    );
}
/Components/Form.tsx
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では、フォームの部分と、タイトルで今回は分けています。

page.tsx
<Title/>
<Form/>

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

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

○propsについて

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

/Components/Form.tsx
interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}

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

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

/Components/CompleteList.tsx
interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}

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

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

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

/Components/Form.tsx
 <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要素にそれぞれ作成したメソッドを割り当てています。
それぞれのメソッドについて説明します。関係する箇所を抜粋しながら説明します。

/Components/Form.tsx
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('')でボタンを押した後に、フォームの中身を空にする処理を行います。

/Components/Form.tsx
interface Todo {
    task: string;
    isCompleted: boolean;
}

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

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

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

○表示部分

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

/Components/CompleteList.tsx
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で型を定義

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

/Components/CompleteList.tsx
interface Todo {
    task: string;
    isCompleted: boolean;
}

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

/Components/CompleteList.tsx
interface CompleteListProps {
    textList: Todo[];
    onDelete: (index: number) => void;
}

次は表示部分です。

/Components/CompleteList.tsx
<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する処理を書いています。

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

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

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

最後に

説明がわかりにくくなってしまった箇所が多くなってしまいすみません。
他にも記事書いているので、よければ見てみてください。
基本設計について
【Node.js】Node.jsでAPIを作ってデータを操作する。
Vue.jsとNode.jsでチャットアプリを作った

6
3
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
6
3