Next.jsとMicroCMSでブログサイトを作成するーPart3（応用編）

[Next.jsとMicroCMSでブログサイトを作成するーPart2（基本編）](https://qiita.com/qn-takami/items/e98b55caa000616e49de)の続きです。

:::note info
こちらも合わせてどうぞ
[Next.jsとMicroCMSでブログサイトを作成するーPart1（準備編）](https://qiita.com/qn-takami/items/e6c72e1688fd1cc5af18)
[Next.jsとMicroCMSでブログサイトを作成するーPart2（基本編）](https://qiita.com/qn-takami/items/e98b55caa000616e49de)
:::

## 記事を編集する
記事を編集するようフォームを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/cae6009f-3df2-5488-8aa5-6b7a23f0da75.png "編集フォーム")

同じく以下のようなコードになります。コードは/pages/blog/[id]というディレクトリを作成してその配下に置きます。

```pages/blog/[id]/update.js
import Link from "next/link";
import { client } from "@/libs/client";
import { useRouter } from "next/router";
import React, { useState } from "react";
export default function Update ({ blog }) {
  const router = useRouter()
  const id = blog.id;
  const [title, setTitle] = useState(blog.title);
  const [content, setContent] = useState(blog.content);
  const remove_html = text => {
    return text ? text.replace(/(<([^>]+)>)/gi, '') : null;
  }
  async function update(event) {
    event.preventDefault()
    client.update({
      endpoint: "blogs",
      content: {
        title: title,
        content: content,
      },
      contentId: id
    }).then((res) => {
      if (res.status === 401) {
        alert("編集ができませんでした。")
      } else {
        alert("編集されました。")
        router.push(`/`)
      }
    })
  }
  return (
    <>
      <div className='main'>
        <h1 className=''>編集ページ</h1>
        {/* TOPに戻るボタン */}
        <div className="top">
          <Link href="/" className="">
            <button className="">Topに戻る</button>
          </Link>
        </div>
        <div className="contents">
            <div>
                <form onSubmit={ update }>
                  <div>
                      <h2>タイトル</h2>
                      <input name="title" type="text" defaultValue={blog.title} onChange={(event) => setTitle(event.target.value)} />
                  </div>
                  <div>
                  <h2>コンテンツ</h2>
                      <textarea name="content" defaultValue={remove_html(blog.content)} onChange={(event) => setContent(event.target.value)} ></textarea>
                  </div>
                <button type="submit">
                    <p>登録する</p>
                </button>
                </form>
            </div>
        </div>
      </div>
    </>
  );
}
export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: "blogs" });
  const paths = data.contents.map((content) => `/blog/${content.id}/update`);
  return { paths, fallback: false };
};
export const getStaticProps = async (context) => {
  const id = context.params.id;
  const data = await client.get({ endpoint: "blogs", contentId: id });
  return {
    props: {
      blog: data,
    },
  };
};
```

編集画面は新規作成記事と似た構成になりますがデータを取得（getStaticProps）してuseState でセットしているところが違いになります。また
`<input name="title" type="text" defaultValue`
とセットしているところにはdefaultValueが 使われているところも確認しましょう。

うまく動けば既存記事の編集が可能です。

## 記事を削除する
一覧から記事を削除できるようにします。

index.jsを編集してボタンを付けてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/bdb61d0f-5f51-e826-8ec2-92724a4484dd.png "削除ボタン追加")

```pages/index.js
import Head from "next/head";
import Link from "next/link";
import styles from "@/styles/Home.module.css";
import { client } from '@/libs/client'
import { useRouter } from "next/router";
export default function Home({ blog }) {
  const router = useRouter();
  async function deleteItem(event) {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    const id = formData.get('id');
    client.delete({
      endpoint: 'blogs',
      contentId: id,
    })
      .then((res) => {
        alert("正常に削除されました。")
        router.push(`/`)
      })
      .catch((err) => {
        alert("削除できませんでした。"+err)
      })
  }
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div id="new-blog">
          <Link href="/blog/create" className="">
            <button className="">ブログを作成</button>
          </Link>
        </div>
        <div id="blog-list">
          {blog.map((blog) => (
            <div className={styles.div}>
              <ul className={styles.ul}>
                <li className={styles.li} key={blog.id}>
                  <Link href={`/blog/${blog.id}`}>{blog.title}</Link>
                  <Link href={`/blog/${blog.id}/update`} className="">
                    <button className="">編集</button>
                  </Link>
                  <form onSubmit={ deleteItem }>
                    <input type="hidden" name="id" value={blog.id} />
                    <input type="submit" value="削除" />
                  </form>
                </li>
              </ul>
            </div>
          ))}
        </div>
      </main>
    </>
  )
}
export const getStaticProps = async () => {
  const data = await client.get({
    endpoint: 'blogs',
  })
  return {
    props: {
      blog: data.contents,
    },
  }
}
```

ソース上にはdeleteItem という関数をつけて削除処理を行っています。押したら記事が削除されることを確認しましょう。

コンポーネントを使ってコードの可読性を上げる
ここまで記事作成、編集、削除のコードを見てきましたが、HTMLのコードとJavascriptのコードの混在というのは時に読みにくく、ページ内に多くの情報があればあるほど可読性は下がります。

そこでコンポーネントという概念を使ってパーツ単位に分けて小分けに構成するというのがNext.js/Reactの流儀となります。イメージとしては以下のようになります。

pages/index.js

↓（コンポーネント呼び出し）

/component/List.js

index.jsはこのように記載して
```
import Head from "next/head";
import List from '../components/List'
import { client } from '@/libs/client'
import Link from "next/link";
import styles from "@/styles/Home.module.css";
export default function Home({ blog }) {
  return (
    <>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <main className={styles.main}>
        <div id="new-blog">
        <Link href="/blog/create" className="">
            <button className="">ブログを作成</button>
        </Link>
        </div>
        <List blog={blog} />
      </main>
    </>
  )
}
export const getStaticProps = async () => {
  const data = await client.get({
    endpoint: 'blogs',
  })
  return {
    props: {
      blog: data.contents,
    },
    revalidate: 1
  }
}
```
List.jsはこのような形となります。
```
import React from 'react'
import Link from "next/link";
import styles from "@/styles/Home.module.css";
import { client } from '@/libs/client'
import { useRouter } from "next/router";
function List( {blog} ) {
    const router = useRouter();
    async function deleteItem(event) {
        event.preventDefault();
        const formData = new FormData(event.currentTarget);
        const id = formData.get('id');
        client.delete({
          endpoint: 'blogs',
          contentId: id,
        })
          .then((res) => {
            alert("正常に削除されました。")
            router.push(`/`)
          })
          .catch((err) => {
            alert("削除できませんでした。"+err)
          })
    }
    return (
        <div id="blog-list">
        {blog.map((blog) => (
            <div className={styles.div}>
            <ul className={styles.ul}>
                <li className={styles.li} key={blog.id}>
                <Link href={`/blog/${blog.id}`}>{blog.title}</Link>
                <Link href={`/blog/${blog.id}/update`} className="">
                    <button className="">編集</button>
                </Link>
                <form onSubmit={ deleteItem }>
                    <input type="hidden" name="id" value={blog.id} />
                    <input type="submit" value="削除" />
                </form>
                </li>
            </ul>
            </div>
        ))}
        </div>
    )
}
export default List;
```

getStaticProps で取得したPropの blog を子に当たるList.jsに引き渡しています。このようにしてパーツごとの再利用性を上げつつ可読性を上げるというのがComponentとなります。

変更点を出すとこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/aa292abc-7e39-c4dc-4f10-142a9676d8e1.png)

## Githubにソースを上げる
ここまで作成したソースをGithubにあげてみましょう。

ちなみに .env.development.local は.gitignoreに記載し、上げないようにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/ee1d0b88-10fe-6a5a-9e6e-dbec04b60196.png)
.gitigonore

Githubへのコードのアップはこちらを参考にしてみてください。
https://qiita.com/aisplay/items/5a77e259ea2feeeb48c3

## Vercelでアプリケーションを公開しよう
Vercelを使ってアプリケーションを公開しましょう。Githubに上げたものをVercelで選択するだけでデプロイされます。

まずはVercelにアクセスします。Hobbyでアカウント名をなにか決めて、Githubのアカウントでサインインします。
https://vercel.com/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/d19e1569-254b-c815-c78f-f14299e8b5c4.png)

Githubのアカウントを指定するとこちらの画面になるので、自分で作ったアプリケーションを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/f55b162f-61b3-fa89-39f3-81b3523ec5b7.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/4f3f2314-a30a-1f35-029a-b6be5adaf05e.png)

https://next-microcms-tau.vercel.app/

環境変数の設定を行う必要があります。これは.env.development.local に記載されているもので大丈夫です。以下の画面から行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/ca8d9707-d4d0-1037-ba31-1b0ccdaf77e5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/e58c73cd-6abc-095d-5f22-2648755ff926.png)

ここまでできれば後は自動でデプロイされます。うまくいくとこうなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/7b028981-3310-5439-35c1-f844ef3b3e45.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/3bf73f5c-34c0-654f-04db-20d41ac8dd7a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821455/711276ab-e9e4-e1ea-d77c-6a18c5ce2199.png)

Visitのボタンを押すと自分が上げたアプリが動きます。ぜひ確認してみましょう。

## 終わりに
ここまでいかがでしょうか？作業の過程で色々エラーが出るかもしれません。調べてみて、わかったことはこちらのドキュメントにコメントいただけると幸いです。長文にもかかわらず読了いただきありがとうございました。