Next.jsで簡単なTodoページを作成してみた

## 初めに
今回、久しぶりにプログラミングの勉強をしようということで流行りのReactを勉強しました。
どうせならNext.jsも使って簡単なものを作ろうと思い、今回はTodoアプリを作成しました。

## 環境
- Docker
- Next.js
- TypeScript
- TailwindCSS

## Dockerでの環境構築
ネットでたくさん出回っていますが、私は下記サイトを参考にしました。
https://zenn.dev/tasuya/articles/da033574b85e6d

## ヘッダーを追加する
ほとんどのサイトではどのページに行っても、ヘッダーがあると思うので、ヘッダーを作成します
```sh
$ mkdir components
$ touch ./components/Header.tsx
```

Header.tsxの中身は下記です(タイトルをクリックするとトップページに戻れるようにリンクを設定しています)
```tsx:Header.tsx
import Link from 'next/link'

export default function Header() {
  return (
    <header className="text-gray-600 body-font bg-blue-500">
      <div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
        <Link href={'/'} passHref>
          <a className="flex title-font font-medium items-center text-gray-900 mb-4 md:mb-0">
            <span className="ml-3 text-xl text-white">My Blog</span>
          </a>
        </Link>
        <nav className="md:ml-auto flex flex-wrap items-center text-base justify-center">
          <a className="mr-5 text-white hover:text-gray-900">Profile</a>
        </nav>
      </div>
    </header>
  )
}
```

最後にヘッダー部分を全体のページに適用させるように_app.tsxを下記に修正
```diff_tsx:_app.tsx
import '../styles/globals.css';
import type { AppProps } from 'next/app';
+ import Header from '../components/Header';

function MyApp({ Component, pageProps }: AppProps) {
-  return <Component {...pageProps} />;
+  return (
+    <>
+      <Header />
+      <Component {...pageProps} />
+    </>
+  );
}

export default MyApp;
```

画像のようになっていれば成功です。
![ヘッダー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146391/b0ef9183-1223-66cc-cc99-6f4b12289205.png)

## Todoの作成

では、本題のTodoページを作成していきたいと思います。
最初にコンポーネント(部品)作っていこうと思います。
シンプルに2つの部品を作っていきます。
- Todoのリスト
- Todoを追加するためのフォーム

では、componentsディレクトリ直下にファイルを作成します。
```sh
$ touch ./components/{todoForm.tsx,todoList.tsx}
```
Todoのリストの方を下記にする
※画像は「./public/ハリネズミ.jpg」に配置する(画像名は自分で決めても問題ないです)
```tsx:todoList.tsx
import type { Todo } from "../types/todolist"

type Props = {
  todolist: Todo
}

export default function TodoList( { todolist }: Props) {
  return (
    <>
      <div className="p-2 lg:w-1/3 md:w-1/2 w-full">
        <div className="h-full flex items-center border-gray-200 border p-4 rounded-lg">
          <img
            className="w-16 h-16 bg-gray-100 object-cover object-center flex-shrink-0 rounded-full mr-4"
            src="/ハリネズミ.jpeg"
            alt="icon"
          />
          <div className="flex-grow">
            <h2 className="text-gray-900 title-font font-medium">{ todolist.title}</h2>
            <p className="text-gray-500">{todolist.body}</p>
          </div>
        </div>
      </div>
    </>
  )
}
```
Todoを追加するためのフォームを下記にする
```tsx:todoForm.tsx
export default function TodoForm( {handleadd} ) {
  return (
    <>
      <form className="font-sans text-sm rounded w-full max-w-md mx-auto my-8 px-8 pt-6 pb-8" onSubmit={handleadd}>
        <div className="relative border rounded mb-4 shadow appearance-none label-floating">
          <input className="w-full py-2 px-3 text-gray-700 leading-normal rounded" id="todotitle" type="text" placeholder="todotitle" name="todotitle" />
          <label className="absolute block text-gray-700 top-0 left-0 w-full px-3 py-2 leading-normal">
          </label>
        </div>
        <div className="flex items-center justify-between">
          <input type="submit" value="投稿" />
        </div>
      </form>
    </>
  )
}
```
次にTodoの構成を型宣言していく
新たにtypesディレクトリを作成し、todolist.tsを作成する
```sh
$ mkdir ./types
$ touch ./types/todolist.ts
```
todolist.tsの値を下記にする
```ts:todolist.ts
export type Todo = {
  id: number
  createdAt: string
  title: string
  body: string
  tag: string
}
```
pegesディレクトリ直下にtodoディレクトリを作成し、その直下にindex.tsxを作成します。
```sh
$ mkdir ./pages/todo
$ touch ./pages/todo/index.tsx
```

index.tsxを下記にする
```tsx:index.tsx
import TodoList from "../../components/todoList"
import type { Todo } from "../../types/todolist"
import TodoForm from "../../components/todoForm"
import React, { useState } from "react"

type Props = {
  todolists: Array<Todo>
}

export default function Index({ todolists }: Props) {

  const [todos, settodo] = useState(todolists)

  const handle_add = async (event:any)=>{
    event.preventDefault()
    const data = {
      id: todos.length + 1,
      createdAt: "2022-02-02",
      title: event.target.todotitle.value,
      body: "テストテスト",
      tag: "特になし"
    }
    settodo([ ...todos, data])
    console.log(todolists)
    event.target.todotitle.value=''
  
  }
  return (
    <>
      <section className="text-gray-600 body-font">
        <div className="container px-5 py-24 mx-auto">
          <TodoForm  handleadd = {handle_add} />
          <div className="flex flex-col text-center w-full mb-20">
            <h1 className="sm:text-3xl text-2xl font-medium title-font mb-4 text-gray-900">Todo</h1>
          </div>
          <div className="flex flex-wrap -m-2">
            { todos.map(todolist =>(
              <TodoList todolist={todolist}></TodoList>
            ))}
          </div>
        </div>
      </section>
    </>
  )
}

export const getStaticProps = async () => {
  const url = process.env.JSON_FILE
  const req = await fetch( url )
  const json = await req.json()

  return {
    props: {
      todolists: json
    }
  }
}
```
最後にTodoの内容を記載したjsonファイルを配置し、envファイルを修正する
下記ディレクトリにファイルを配置
```sh
$ touch ./public/todo.json
```
jsonファイルを下記にする
```json:todo.json
[
  { "id": 1,
    "created_at": "2022-02-11T15:59:15.693Z",
    "title": "todo1",
    "body": "テストです1",
    "tag": "特になし"
  },
  { "id": 2,
    "created_at": "2022-02-11T15:59:15.693Z",
    "title": "todo1",
    "body": "テストです2",
    "tag": "特になし"
  },
  { "id": 3,
    "created_at": "2022-02-11T15:59:15.693Z",
    "title": "todo1",
    "body": "テストです3",
    "tag": "特になし"
  }
]
```
envファイルを作成し、内容を記載する(envファイルとは環境変数を入れるファイルのこと)
※3000のところは設定したポート番号を記載してください。
```sh
$ touch ./public/.env.local
```
```text:env.local
JSON_FILE=http://localhost:3000/todo.json
```

## Todoページにアクセス
画像のようになっていれば成功です
![todoページトップ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146391/6f9065fd-c2fc-603d-acbf-794163a87936.png)

テキストボックスに「テスト」と入力して「投稿」をクリックするとtodoが追加される
![todo追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146391/294116b3-fd7b-e8f9-cd9c-882d6eb3e47f.png)

## まとめ
今回ですが、簡単なTodoサイトを作成しました。
jsonのところをデータベースから取得するようにすれば書き換え可能なTodoサイトを作成できます。
TypeScriptで書いてるのですが、所々型宣言が抜けているところがあるため、学習を今後も続けていきたいです。
普段はPHP,Rubyを書いてることが多いですが、TypeScriptも楽しいですね〜
トレンドであるgolangとの連携もやってみたい。