MENU

microCMSでCRUD処理を実装する

今回は、microCMSでWebアプリを作り、新規投稿や更新、削除などのCRUD処理を搭載する手順をまとめたいと思います。

microCMSとのデータのやり取りはAPIを使い、フロントエンドはNext.jsで作成します。

microCMSのドキュメントには、CRUD処理がまとめられていますが、独学者、初学者目線でまとめていきたい思います。

目次

microCMSの設定

まずはmicroCMSの設定を行います。

サービスの作成

下記のようにサービス作成の画面からサービス名を決め、「サービスを作成する」を押します。
これでサービスが作成されます。

サービス作成画面

APIの作成

次に今回のCRUD処理をするためのAPI を作成していきます。

以下の画面から、「自分で決める」を選び、進みます。

APIの作成

「自分で決める」以外を選ぶと、ブログやお知らせなどmicroCMS側でデフォルト用意している、最低限必要なAPIスキーマを自動作成してくれます。

API名とエンドポイント

次にAPIの初期設定として、API名エンドポイントを決定します。
今回はサンプルアプリに合わせて、API名を「サンプル」、エンドポイントはsampleとします。

API名とエンドポイント

API名とエンドポイントは後からでも更新できますが、しっかりとサービスを作成していく場合は、ある程度実装するサービスの内容と一致させるように設定をすることが重要です。
後々、エンドポイントを変更するとなると、様々な箇所で変更が生じる可能性があるためです。

APIの形式

次に、APIの形式を設定します。
APIの形式として選択できるのは、「リスト形式」か「JSON形式」になります。
今回は「リスト形式」を選択し、webアプリを作成していきます。

APIの形式を選択する

APIスキーマ

最後にAPIスキーマを定義していきます。
今回は、サンプルアプリを作成し、CRUD処理を実装していくことが目的ですので、シンプルなAPIスキーマ(下記のとおり)を定義します。

設定するAPIの内容

APIスキーマは、当初設定した内容に加え、後から追加、削除、編集が可能です。
ただし、すでに登録済みのコンテンツがある場合、登録内容のデータの形式が変更後のAPIで設定したフィールドのデータ型に一致しない場合、エラーとなってしまう可能性がありますので、注意してください。

ちなみに、設定できるフィールドは、以下のように選択が可能です。
今回はCRUD処理をメインに考えていますので、詳細はドキュメントを参照してください。

APIで設定できるフィールド

CRUD処理の権限の追加

次にCRUD処理を実装していくための権限設定を行います。
作成したAPIの左上にある「歯車マーク」から下記のような画面を表示し、「APIキー」を選択します。

APIキーから権限設定を行う

次に、APIキー管理画面が表示されたら、作成したAPIキーを選択します。

APIキーの管理画面

最後に下記の画像の赤枠の箇所の、POSTPUTPATCHDELETEのチェックボックスにチェックをつけ、デフォルト権限を変更し、保存(右上の「変更」ボタンを押す)します。

デフォルト権限の設定

以上で、microCMS側でのCRUD処理の設定は完了です。

この後、コンテンツ確認をしていくため、microCMS側からいくつかコンテンツを追加しておいてください。

Next.js(フロントエンド)の準備

続いて、microCMSで作成したAPIを使用して配信するデータを表示するフロントエンド側を作成していきます。
まずは、以下のコマンドからWebアプリケーションを作成します。

npx create-next-app your_app_name

上記のコマンド実行にあたり、以下の2点について注意してください。

  1. 上記のコマンドを実行すると、TypeScriptの使用について聞かれますが、筆者はまだTypeScriptの学習が不十分であるため、今回のサンプルアプリでは、TypeScriptは使用しません。
    ご了承ください。
  2. ESLintは使用する設定でコマンドを実行してください。

作成できたら、package.jsonのあるディレクトリへ移動し、以下のコマンドで開発用サーバーを立ち上げます。

npm run dev

以下のように画面が表示されるか確認しましょう。

http://localhost:3000のイメージ

microcms-js-sdkの設定

公式ドキュメントの「microcms-js-sdkの初期化」に従い、初期設定をしていきます。
この設定については、ドキュメントどおりですので、こちらを参照し、作成してください。

念の為、私の作成したファイルはこのようにしています。

NEXT_PUBLIC_API_KEY=your_api_key
NEXT_PUBLIC_SERVICE_DOMAIN=your_service_domain
import { createClient } from 'microcms-js-sdk';

export const client = createClient({
  serviceDomain: process.env.NEXT_PUBLIC_SERVICE_DOMAIN,
  apiKey: process.env.NEXT_PUBLIC_API_KEY,
});

GETメソッドで投稿内容の一覧を取得する

まずは、投稿内容の一覧を取得するGETメソッドを設定します。
詳細は、以前に投稿したこちらの記事や公式ブログを参考に設定してください。

今回のサンプルアプリの設定内容は、以下のとおりです。

import Link from "next/link";
import { client } from "../libs/client";

export default function Home({ sample }) {
  return (
    <div>
      <ul>
        {sample.map((sample) => (
          <li key={sample.id}>
            <Link href={`/post/${sample.id}`}>
              <p>{sample.title}</p>
            </Link>
            <div>{ sample.content }</div>
          </li>
        ))}
      </ul>
    </div>
  );
}

// データの取得
export const getStaticProps = async () => {
  const data = await client.get({ endpoint: "sample" });

  return {
    props: {
      sample: data.contents,
    },
  };
};

また、以下のように表示されていれば、GETメソッドについては完成です。

編集後の表示内容

Next.jsにおける状態管理の設定

GETメソッドが利用できるようになったところで、次にPOSTPATCHDELETEの各メソッドを実装していくわけですが、その前に今回のサンプルアプリで各メソッドを実装する前に設定しておくべきものがあります。

それが、「状態管理」です。

これから実装するのは、各コンポーネント間でデータを共有するための、ContextAPIの設定についてになります。

本来、React.js(Next.js)において、親コンポーネントから子コンポーネントへデータを渡す場合、通常はPropsを使用してデータが渡されます。
しかし、複数のコンポーネントがネストされる形でページが構成された場合、構成が複雑化し、Propsを用いてデータを渡していくと、データのバケツリレーとなってしまいます。

これでは、データの渡し方としては、最善の方法ではありません。

そこで、React.js(Next.js)で用意されているContextAPIを利用し、Propsを使用したデータの渡し方ではなく、データをグローバルに管理し、より下層のコンポーネントにおいてもContextAPIで格納されたデータにアクセスできるようにすることで、シンプルなデータへのアクセスを構築することができます。

ContextAPIの設定

それでは早速設定をしていきます。
まずは、プロジェクトディレクトリの直下にlibsディレクトリを作成し、その直下にStateContext.jsを作成し、以下のように記述します。

import { createContext, useState } from "react"

export const StateContext = createContext()

export default function StateContextProvider (props) {
  const [selectedContent, setSelectedContent] = useState({
    // 初期値の設定
    id: 0,
    title: "",
    content: "",
  })

  return (
    <StateContext.Provider
      value={{
        selectedContent,
        setSelectedContent
      }}
    >
      { props.children }
    </StateContext.Provider>
  )
}

まずは、reactからcreateContext及びuseStateimportしています。
また、importしたcreateContextからStateContextオブジェクトを作成します。

import { createContext, useState } from "react"

export const StateContext = createContext()

createContextについてはこちらを参照してください。

useStateについては、こちらを参照してください。

このファイルの中では、StateContextProviderという関数を作成し、useStateを用いて、状態管理用のstateを作成します。

また、selectedContentで保持するデータの初期値として、いくつかの項目を必要に応じ、設定します。

export default function StateContextProvider (props) {
  const [selectedContent, setSelectedContent] = useState({
    // 初期値の設定
    id: 0,
    title: "",
    content: "",
  })
}

selectedContentは、状態管理用の関数です。
つまり、selectedContentは、ページが表示された際にデータを保持する関数です。

setSelectedContentは、更新用のデータ管理の関数です。
selectedContentにより格納されたデータを更新する際に、更新後の値を管理する関数です。

そして、StateContext.Providerを返すようにしています。

return (
  <StateContext.Provider
    value={{
      selectedContent,
      setSelectedContent
    }}
  >
    { props.children }
  </StateContext.Provider>
)

この場合、{ props.children }の場所には、StateContextProviderの配下に配置されたコンポーネントがレンダリングされ、valueに設定したselectedContent及びsetSelectedContentが渡されます。
これにより、各コンポーネントでselectedContent及びsetSelectedContentにアクセスし、データを取得することができるようになります。

それでは、ここで定義したStateContextProvider関数を以下のように_app.jsに追加します。

import '../styles/globals.css'
import StateContextProvider from '../context/StateContext'

function MyApp({ Component, pageProps }) {
  return (
    <StateContextProvider>
      <Component {...pageProps} />
    </StateContextProvider>
  ) 
}

export default MyApp

これで各ページで配置したコンポーネントでContextにアクセスすることができるようになりました。

POSTメソッド

それでは、ここからはPOSTメソッドを実装していきます。
microCMSから提供されているmicrocms-js-sdkで利用できるメソッドの一つにPOSTメソッドがあります。
このPOSTメソッドの使い方の基本形は、以下のとおりです。

//コンテンツを作成する
client
  .create({
    endpoint: 'endpoint', //作成したAPIのエンドポイント(今回のsample_appでは「sample」となる)
    content: {
      title: 'title',
      body: 'body',
    },
  })
  .then((res) => console.log(res.id))
  .catch((err) => console.error(err));

なお、詳細は以下の公式のブログもご覧ください。

それではこの基本形をベースに今回のsample_appでのPOSTメソッドを実装していきます。

post.jsの作成

まずは、pagesディレクトリの直下にpost.jsを作成し、コンテンツの投稿ページを作成します。

import PostForm from "../components/PostForm"
import Link from "next/link"

export default function Post () {

  return (
    <>
      <div className=''>
        <h1 className=''>投稿ページ</h1>

        {/* TOPに戻るボタン */}
        <div className="">
          <Link href="/" className="">
            <button className="">Topに戻る</button>
          </Link>
        </div>

        <div className="">
          <PostForm />
        </div>
      </div>
    </>
  );
}

PostForm.jsはこの後作成します。
一度エラーが出ますが、後ほど解消します。

PostForm.jsの作成

次に、post.jsで配置するPostForm.jsというコンポーネントを作成します。
sample_appディレクトリの直下にcomponentsディレクトリを作成し、そのcomponentsディレクトリの中にPostForm.jsを作成します。
PostForm.jsの中身は以下のようにします。

import { client } from "../libs/client";
import { useRouter } from "next/router"
import { useContext } from "react";
import { StateContext } from "../context/StateContext";

const PostForm = () => {
  const { selectedContent, setSelectedContent } = useContext(StateContext)

  const router = useRouter()
  console.log(selectedContent)
  const create = async (e) => {
    e.preventDefault();

    client.create({
      endpoint: "sample",
      content: {
        title: selectedContent.title,
        content: selectedContent.content,
      }
    }).then((res) => {
      if (res.status === 401) {
        alert("登録ができませんでした。")
      } else {
        alert("登録されました。")
        router.push(`/`)
      }
    })
  }

  return (
    <>
      <div>
        <h2>タイトル</h2>
        <input
          type="text"
          onChange={(e) => setSelectedContent({
            ...selectedContent,
            title: e.target.value
          })}
        />
        
      </div>
      <div>
      <h2>コンテンツ</h2>
        <textarea
          onChange={(e) => setSelectedContent({
            ...selectedContent,
            content: e.target.value
          })}
        ></textarea>
      </div>

      <div>
        <form onSubmit={ create }>
          <button type="submit">
            <p>登録する</p>
          </button>
        </form>
      </div>
    </>
  )
}

export default PostForm

それでは、PostForm.jsの中身を見ていきます。

まずは、import関係です。

import { client } from "../libs/client";
import { useRouter } from "next/router"
import { useContext } from "react";
import { StateContext } from "../context/StateContext";

これまでに作成したlibs/client.jscontext/StateContext.jsimportしています。
また、reactnextで標準で搭載されているuseRouteruseContextも併せてimport しておきます。

useContextuseRouterについては、こちらの公式ドキュメントを参考にしてください。

次に、PostFormの中身を見ていきます。

const PostForm = () => {
  return (
    <>
      <div>
        <h2>タイトル</h2>
        <input
          type="text"
          onChange={(e) => setSelectedContent({
            ...selectedContent,
            title: e.target.value
          })}
        />
        
      </div>
      <div>
      <h2>コンテンツ</h2>
        <textarea
          onChange={(e) => setSelectedContent({
            ...selectedContent,
            content: e.target.value
          })}
        ></textarea>
      </div>

      <div>
        <form onSubmit={ create }>
          <button type="submit">
            <p>登録する</p>
          </button>
        </form>
      </div>
    </>
  )
}

export default PostForm

中身はシンプルにtitlecontentを入力するためのinputフォームとtextareaを配置しています。
そしてinputフォームとtextareaにはonChangeを設定しています。

onChangeでは、inputフォームやtextareaに内容が入力された(各フォームに変更が生じた)場合に発生するイベントを設定しています。

setSelectedContentにより、
selectedContentのオブジェクト(...selectedContent)の
titleまたはcontentの内容を、入力された値(e.target.value)に更新するように設定しています。

更新された内容はsetSelectedContentで常に監視し、更新がされる度に、selectedContentの値を変更しています。

onChangeについては、こちらも参考にしてください。

POSTメソッドの設定

次に、実際のPOSTメソッド(microcms-js-sdkではcreate関数で設定されている)を設定していきます。

  const { selectedContent, setSelectedContent } = useContext(StateContext)

  const router = useRouter()
  const create = async (e) => {
    e.preventDefault();

    client.create({
      endpoint: "sample",
      content: {
        title: selectedContent.title,
        content: selectedContent.content,
      }
    }).then((res) => {
      if (res.status === 401) {
        alert("登録ができませんでした。")
      } else {
        alert("登録されました。")
        router.push(`/`)
      }
    })
  }

内容は、公式ドキュメントで示されている内容を少し調整した内容となっています。
公式の内容に従いendpointcontentの内容を送信しています。

selectedContentの値は、setSelectedContent関数で値が入力されるたびに更新されているため、登録時に送信するデータはselectedContentの各データを送信することになります。

そして、返されたステータスコードに応じて、通知内容を変更するよう条件分岐を設定しています。
また、併せてuseRouterを使用し、正しく登録された場合の処理についても記述しています。
ここでは、一覧ページ(=index.js)を表示するように設定しています。

この内容で、登録ページから各フォームの内容を入力し、「登録する」を押すと、正しく登録されると思います。
登録されたら、microCMSの登録内容も併せて確認してみてください。

入力して…
このように登録されれば成功です

PATCHメソッド

次に見ていくのはUPDATEPATCH)メソッドです。
基本的にPOSTメソッドと変わりませんが、UPDATEPATCH)メソッドでは、すでに登録している内容を取得し、その内容を変更して再度登録する作業となりますので、保持しているデータがあるという点においてPOSTメソッドとは少々異なります。
この点を踏まえて、UPDATEPATCH)メソッドを実装していきます。

ちなみにPUTメソッドというものもありますが、こちらはcontentIdを任意に設定しながら登録する場合に使用されます。
例えば、contentIdとユーザーネームやアカウント名として使用する場合などが想定されると思います。

詳細ページの作成

今回のUPDATEPATCH)メソッドを実装するにあたっては、コンテンツの詳細ページを作成し、その詳細ページ上で編集ができるように設定していきます。

まずは、登録したコンテンツ毎に詳細ページが表示できるようにページ表示用のファイルを作成していきます。
これは公式ドキュメントでも使われているように、ブログの詳細ページを表示する方法と同じ方法を用いて作成します。

作成したページは以下のとおりです。

import { client } from "../../libs/client";
import Link from "next/link"
import UpdatePost from "../../components/UpdatePost" //後から作成します

export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: "sample" })
  
  const paths = data.contents.map((content) => `/post/${content.id}`)
  return {
    paths,
    fallback: false
  }
}

export const getStaticProps = async (context) => {
  const id = context.params.id
  const data = await client.get({
    endpoint: "sample",
    contentId: id
  })

  return {
    props: {
      content: data,
    }
  }
}


export default function Detail ({ content }) {
  return (
    <>
      <h2>【投稿の詳細ページです】</h2>
      <div>
        <Link href="http://localhost:3000/">
          <p>TOPに戻る</p>
        </Link>
      </div>

      <div>
        <UpdatePost content={ content } />
      </div>
    </>
  )
}

それではそれぞれの項目を見ていきます。
今回は詳細ページを作成するため、各コンテンツのcontentIdをURLに含める形で各コンテンツの詳細ページを作成していきます。

ただし、このcontentIdは各コンテンツに割り振られる一意の値であり、他のcontentIdとは重複しないため、選択したコンテンツ毎に動的に変化します。

これに対応するために、pages/postディレクトリに配置した詳細ページ用のファイルは[ ](ブラケット)をつけたファイル名としています。

これにより、ブラケットで囲われた文字列に選択されたコンテンツのcontentIdが割り振られ、動的にルーティングが構成されることになります。

そして、そのURLを生成するために、コンテンツに割り振られているcontentIdの取得及び取得したcontentIdからそのコンテンツの各項目の内容を取得する関数を作成します。

// pathを生成するためのcontentIdを取得する関数
export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: "sample" })
  
  const paths = data.contents.map((content) => `/post/${content.id}`)
  return {
    paths,
    fallback: false
  }
}

// 取得したcontentIdに紐づく各項目を取得する関数
export const getStaticProps = async (context) => {
  const id = context.params.id
  const data = await client.get({
    endpoint: "sample",
    contentId: id
  })

  return {
    props: {
      content: data,
    }
  }
}

これらの取得方法は、ブログの詳細ページの作成と全く同じです。

作成したAPIによってエンドポイントが異なりますので、その部分だけ注意して作成すれば、問題なくコンテンツを取得できると思います。

そして実際の詳細ページをレンダリングする関数は、以下のとおりです。

export default function Detail ({ content }) {
  return (
    <>
      <h2>【投稿の詳細ページです】</h2>
      <div>
        <Link href="http://localhost:3000/">
          <p>TOPに戻る</p>
        </Link>
      </div>

      <div>
        <UpdatePost content={ content } />
      </div>
    </>
  )
}

今回の詳細ページはUpdatePost.jsというコンポーネントを使用して作成していきます。
本来、内容をAPIから取得し、表示する(=GETメソッド)だけであれば、コンポーネントを使用する必要はないのですが、ここで敢えてコンポーネントを用いるのは、Contextにアクセスする必要があるためです。

Contextへのアクセスのため、StateContextProvider_app.jsで設定しました。
この時、useContextによりselectedContentsetSelectedContentを設定しています。
これらは、コンポーネント間でContextに格納されているデータにアクセスするためのものです。
よって、コンポーネントからContextに格納されているデータにアクセスする必要があるため、敢えてコンポーネント化していることとなります。

今回のUPDATEPATCH)メソッドを実行するにあたり、すでに登録されているコンテンツの各項目を取得し、それを変更して、登録する必要があるため、取得したコンテンツの内容を一時的に保存しているContextにアクセスする必要があるためこのような設定になっています

UpdatePost.jsの作成

それでは、ここからUpdatePost.jsコンポーネントを作成し、実際の表示ページを作成していきます。
作成したファイルはこちらです。

import { client } from "../libs/client";
import { useRouter } from "next/router"
import { useContext, useState } from "react";
import { StateContext } from "../context/StateContext";

const UpdatePost = ({ content }) => {
  const { selectedContent, setSelectedContent } = useContext(StateContext)
  const [isEditing, setIsEditing] = useState(false)

  const router = useRouter()

  // inputフォームのコントロール
  const editingHandler = () => {
    setIsEditing(true)
  }

  //登録内容の更新用の関数
  const update = async (e) => {
    e.preventDefault();
    // 更新処理を記述
    client.update({
      endpoint: "sample",
      contentId: content.id,
      content: {
        title: selectedContent.title,
        content: selectedContent.content,
      }
    }).then((res) => {
      if (res.status === 401) {
        alert("投稿内容の更新ができませんでした。")
      } else {
        alert("更新されました。")
        router.push(`/`)
      }
    })
    //stateの初期化
    setSelectedContent({
      id: 0,
      title: "",
      content: "",
      })
  }

  return (
    <>
      <div>
        <button onClick={() => [editingHandler(), setSelectedContent(content)]}>
          <p>編集する</p>
        </button>
      </div>

      {/* 今回は、スペーサーとして使用 */}
      <div>
        <p></p>
      </div>

      <div>
        <h2>タイトル</h2>
        {!isEditing && <p>{content.title}</p>}
        {isEditing &&
          <div>
            {/* 初期値にselectedContentの各要素を指定→onChangeでsetSelectedContetに更新後の値をe.target.valueで保持 */}
            <input
              value={selectedContent.title}
              type="text"
              placeholder=" "
              onChange={(e) => setSelectedContent({
                ...selectedContent,
                title: e.target.value
              })}
            />
          </div>
        }
      </div>
      <div>
        <h2>コンテンツ</h2>
        {!isEditing && <p>{content.content}</p>}
        {isEditing &&
          <div>
            {/* 初期値にselectedContentの各要素を指定→onChangeでsetSelectedContetに更新後の値をe.target.valueで保持 */}
            <input
              value={selectedContent.content}
              type="text"
              placeholder=" "
              onChange={(e) => setSelectedContent({
                ...selectedContent,
                content: e.target.value
              })}
            />
          </div>
        }
      </div>
      <form onSubmit={ update }>
        <div>
          <button type="submit">
            <p>更新する</p>
          </button>
        </div>
      </form>
    </>
  )
}

export default UpdatePost

まずは、設定した関数を見ていきます。

  const { selectedContent, setSelectedContent } = useContext(StateContext)
 const router = useRouter()
  const [isEditing, setIsEditing] = useState(false)

  // inputフォームのコントロール
  const editingHandler = () => {
    setIsEditing(true)
  }

  //登録内容の更新用の関数
  const update = async (e) => {
    e.preventDefault();
    // 更新処理を記述
    client.update({
      endpoint: "sample",
      contentId: content.id,
      content: {
        title: selectedContent.title,
        content: selectedContent.content,
      }
    }).then((res) => {
      if (res.status === 401) {
        alert("投稿内容の更新ができませんでした。")
      } else {
        alert("更新されました。")
        router.push(`/`)
      }
    })
    //stateの初期化
    setSelectedContent({
      id: 0,
      title: "",
      content: "",
      })
  }

まずは、selectedContentsetSelectedContentを使用するため、useContextを使用し、これらの関数を読み込んでいます。
また、以降の関数でrouterを使用しますので、前回同様useRouterを使用し、routerを定義しています。

const { selectedContent, setSelectedContent } = useContext(StateContext)
const router = useRouter()

次に、inputフォームの挙動を制御するための関数を作成します。

const [isEditing, setIsEditing] = useState(false)

// inputフォームのコントロール
const editingHandler = () => {
  setIsEditing(true)
}

useStateinputフォームの表示・非表示の状態を管理するためのstateを定義します。
ここでは、isEditingsetIsEditingとしておきます。
初期値としてfalse(=inputフォームが表示されない状態)を設定しています。

そして、editingHandlerとして、setEditing(状態を更新するための関数)を変更するための関数を設定します。
この関数が実行された場合、setEditingの値がfalseからtrueに切り替わるようにします。
この切り替えにより、inputフォームの表示・非表示を切り替えます。

非表示の状態から表示に切り替えるだけで、非表示にする関数は今回は設定しません。

そして、実際のUPDATEPATCH)メソッドについては、以下のとおりです。

const update = async (e) => {
  e.preventDefault();
  // 更新処理を記述
  client.update({
    endpoint: "sample",
    contentId: content.id,
    content: {
      title: selectedContent.title,
      content: selectedContent.content,
    }
  }).then((res) => {
    if (res.status === 400) {
      alert("投稿内容の更新ができませんでした。")
    } else {
      alert("更新されました。")
      router.push(`/`)
    }
  })
  //stateの初期化
  setSelectedContent({
    id: 0,
    title: "",
    content: "",
    })
}

基本的には、公式ドキュメントが提供している基本形に倣い、記述しています。

contentIdは、selectedContentから取得しても良いと思いますが、contentIdは基本的に登録時に一意の文字列が与えられるものであり、それを任意に変更することは、コンテンツの管理上は想定されないため、コンポーネントに渡されたpropsから取得しています。

そしてcontentで指定しているtitle及びcontentは、基本的にselectedContentの各フィールドになります。
今回のUPDATEPATCH)メソッドでは、すべてのフィールドを更新可能であり、かつ送信対象としています。

更新内容の変更は、selectedContentsetSelectedContentで管理しており、各フィールドの更新状態を常にselectedContentに反映させているため、titlecontentvalueにはselectedContentの各フィールドのvalueを設定しています。

更新内容の投稿後は、ステータスコードにより処理を分けています。
今回、ステータスコードを400としているのは、下記のとおり、microCMSでPATCHメソッドを実行した際に、空欄のまま投稿すると返されるステータスコードが400であるため、このステータスコードを基準に条件分岐させたということです。

あくまで「空欄であった場合」を想定しての設定ですので、実際にはもう少し細かい設定が必要だと思いますが、今回はあくまでサンプルなので割愛します。

そして、最後に、更新内容の登録が完了した場合には、routerを使用し、トップページに遷移することと、inputフォームの値を初期化することとしています。
ここでは、setSelectedContentの値を初期値に更新しています。
編集が完了したコンテンツですので、idも初期値として0としています。

赤枠のタイトル部分をクリックして詳細ページへ
詳細ページではこのような作業をします。
inputフォームに切り替わり、編集可能となります。

以上が、UPDATEPATCH)メソッドとなります。

DELETEメソッド

最後に見ていくのは、DELETEメソッドになります。

DELETEメソッドは、その名のとおり、コンテンツの削除ですので、POSTメソッドやUPDATEPATCH)メソッドに比べると比較的簡単に実装可能です。

今回は、先ほど作成したUpdatePost.jsに追記していく形で進めます。

まずは、全体のコード(追記後)になります。

import { client } from "../libs/client";
import { useRouter } from "next/router"
import { useContext, useState } from "react";
import { StateContext } from "../context/StateContext";

const UpdatePost = ({ content }) => {
  const { selectedContent, setSelectedContent } = useContext(StateContext)
  const [isEditing, setIsEditing] = useState(false)

  const router = useRouter()

  // inputフォームのコントロール
  const editingHandler = () => {
    setIsEditing(true)
  }


  // アイテム削除の関数
  const deleteItem = async () => {
    await client.delete({
      endpoint: 'sample',
      contentId: content.id,
    })
      .then((res) => {
        alert("正常に削除されました。")
        router.push("/")
      })
      .catch((err) => {
        alert("削除できませんでした。")
        router.reload
      })
  }

  //登録内容の更新用の関数
  const update = async (e) => {
    e.preventDefault();
    // 更新処理を記述
    client.update({
      endpoint: "sample",
      contentId: content.id,
      content: {
        title: selectedContent.title,
        content: selectedContent.content,
      }
    }).then((res) => {
      if (res.status === 400) {
        alert("投稿内容の更新ができませんでした。")
      } else {
        alert("更新されました。")
        router.push(`/`)
      }
    })
    //stateの初期化
    setSelectedContent({
      id: 0,
      title: "",
      content: "",
      })
  }

  return (
    <>
      <div>
        <button onClick={() => [editingHandler(), setSelectedContent(content)]}>
          <p>編集する</p>
        </button>
      </div>

      {/* 今回は、スペーサーとして使用 */}
      <div>
        <p></p>
      </div>
      
      <div>
        <h2>タイトル</h2>
        {!isEditing && <p>{content.title}</p>}
        {isEditing &&
          <div>
            {/* 初期値にselectedContentの各要素を指定→onChangeでsetSelectedContetに更新後の値をe.target.valueで保持 */}
            <input
              value={selectedContent.title}
              type="text"
              placeholder=" "
              onChange={(e) => setSelectedContent({
                ...selectedContent,
                title: e.target.value
              })}
            />
          </div>
        }
      </div>
      <div>
        <h2>コンテンツ</h2>
        {!isEditing && <p>{content.content}</p>}
        {isEditing &&
          <div>
            {/* 初期値にselectedContentの各要素を指定→onChangeでsetSelectedContetに更新後の値をe.target.valueで保持 */}
            <input
              value={selectedContent.content}
              type="text"
              placeholder=" "
              onChange={(e) => setSelectedContent({
                ...selectedContent,
                content: e.target.value
              })}
            />
          </div>
        }
      </div>
      <form onSubmit={ update }>
        <div>
          <button type="submit">
            <p>更新する</p>
          </button>
        </div>
      </form>

      {/* 今回は、スペーサーとして使用 */}
      <div>
        <p></p>
      </div>


      <div onClick={deleteItem}>
        <button>
          <p>削除する</p>
        </button>
      </div>
    </>
  )
}

export default UpdatePost

追記したのは、以下の2箇所です。

ひとつ目は、DELETEメソッドの追記です。

// アイテム削除の関数
const deleteItem = async () => {
  await client.delete({
    endpoint: 'sample',
    contentId: content.id,
  })
    .then((res) => {
      alert("正常に削除されました。")
      router.push("/")
    })
    .catch((err) => {
      alert("削除できませんでした。")
      router.reload
    })
}

基本的には、これまでと同様に、公式ドキュメントのとおり作成しています。

DELETEメソッドについては、endpointcontentIdを指定し、delete関数を実行するのみです。

作成されたdeleteItem関数は、削除ボタンのonClickに紐づけています。

関数実行時の挙動としては、エラーをキャッチした場合のみ、ページのリロードをrouterを使用して行なっています。

実際に、詳細ページからコンテンツを削除してみて、正しく動作することを確認してみてください。

まとめ

今回は、microCMSとNext.jsによるWebアプリで基本的なCRUD処理の実装についてみてきました。

microcms-js-sdkを使用することで、処理自体は簡単に実装することができます。

あとは、Next.jsのフロントエンド側での処理を今回の例より詳しく書ければ、よりリッチで制御されたCRUD処理が可能になるのではないかと思います。

独学で学習を進めていても、今回は公式ドキュメントなどをみながら進めることができましたので、microCMSの公式ドキュメントの充実さも素晴らしいものと思います。

今後は、webアプリの作成の選択肢の一つとしてこの構成も候補に入れて、学習を進めていきたいと思います。

長くなりましたが、最後まで読んでいただき、ありがとうございました。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

はじめまして、ふじです。
Python、Django、FastAPI、React.js、Next.jsを学習している、ずっと文系のプログラミング独学者です。

目次