見出し画像

React|TailwindCSSでアプリを作ったのでコードを超詳しく解説

以下のコードは、React と TailwindCSS を使用して、問い合わせ管理と会員登録フォームを提供するシングルページアプリケーション (SPA) の主要コンポーネントです。このコードの機能と各部分の詳細を順を追って解説します。

コード全体像

"use client";
import React from "react";

// メインコンポーネント
function MainComponent() {
  // 状態管理のためのフックを使用
  const [inquiries, setInquiries] = React.useState([
    {
      id: 1,
      name: "Taro Yamada",
      email: "taro@example.com",
      message: "お問い合わせ内容1",
      status: "未対応",
    },
    {
      id: 2,
      name: "Hanako Tanaka",
      email: "hanako@example.com",
      message: "お問い合わせ内容2",
      status: "対応中",
    },
  ]);
  const [selectedInquiry, setSelectedInquiry] = React.useState(null);
  const [status, setStatus] = React.useState("");
  const [newInquiry, setNewInquiry] = React.useState({
    name: "",
    email: "",
    message: "",
  });

  // 問い合わせを編集するための関数
  const handleEditClick = (inquiry) => {
    setSelectedInquiry(inquiry);
    setStatus(inquiry.status);
  };

  // ステータス変更時の関数
  const handleStatusChange = (event) => {
    setStatus(event.target.value);
  };

  // 編集内容を保存する関数
  const handleSave = () => {
    setInquiries(
      inquiries.map((inquiry) =>
        inquiry.id === selectedInquiry.id
          ? { ...inquiry, status: status }
          : inquiry
      )
    );
    setSelectedInquiry(null);
  };

  // 新規問い合わせの入力を管理する関数
  const handleNewInquiryChange = (event) => {
    const { name, value } = event.target;
    setNewInquiry({ ...newInquiry, [name]: value });
  };

  // 新規問い合わせを追加する関数
  const handleNewInquirySubmit = (event) => {
    event.preventDefault();
    setInquiries([
      ...inquiries,
      { ...newInquiry, id: inquiries.length + 1, status: "未対応" },
    ]);
    setNewInquiry({ name: "", email: "", message: "" });
  };

  // ページのスクロール処理
  React.useEffect(() => {
    const handleHashChange = () => {
      if (window.location.hash === "#register") {
        document
          .getElementById("register-page")
          .scrollIntoView({ behavior: "smooth" });
      }
    };

    window.addEventListener("hashchange", handleHashChange);
    return () => {
      window.removeEventListener("hashchange", handleHashChange);
    };
  }, []);

  // 選択された問い合わせがある場合の表示
  if (selectedInquiry) {
    return (
      <div className="p-6">
        <h2 className="text-xl font-bold">お問い合わせ編集</h2>
        <div className="mt-4">
          <p className="font-semibold">名前: {selectedInquiry.name}</p>
          <p className="font-semibold">メール: {selectedInquiry.email}</p>
          <p className="font-semibold">
            メッセージ内容: {selectedInquiry.message}
          </p>
          <div className="mt-4">
            <label className="font-semibold" htmlFor="status">
              ステータス
            </label>
            <select
              id="status"
              name="status"
              className="ml-2 p-2 border"
              value={status}
              onChange={handleStatusChange}
            >
              <option value="未対応">未対応</option>
              <option value="対応中">対応中</option>
              <option value="対応済">対応済</option>
            </select>
          </div>
          <button
            className="mt-4 px-4 py-2 bg-green-500 text-white font-bold"
            onClick={handleSave}
          >
            保存
          </button>
        </div>
      </div>
    );
  }

  // 通常時の表示
  return (
    <div className="flex flex-col lg:flex-row">
      <div className="w-full lg:w-[300px] bg-gray-800 text-white">
        <div className="p-4 text-lg font-bold">Menu</div>
        <ul>
          <li className="p-4 hover:bg-gray-700">
            <a href="#home" className="font-semibold">
              HOME
            </a>
          </li>
          <li className="p-4 hover:bg-gray-700">
            <a href="#register" className="font-semibold">
              会員登録
            </a>
          </li>
          <li className="p-4 hover:bg-gray-700">
            <a href="#inquiries" className="font-semibold">
              お問い合せ一覧
            </a>
          </li>
          <li className="p-4 hover:bg-gray-700">
            <a href="members.html" target="_blank" className="font-semibold">
              会員一覧
            </a>
          </li>
        </ul>
      </div>
      <div className="flex-grow p-6 overflow-auto">
        <div id="register-page">
          <h2 className="text-xl font-bold">会員登録</h2>
          <form className="mt-4">
            <div className="mb-4">
              <label className="block font-semibold" htmlFor="username">
                ユーザー名
              </label>
              <input
                type="text"
                id="username"
                name="username"
                className="mt-2 p-2 w-full border"
              />
            </div>
            <div className="mb-4">
              <label className="block font-semibold" htmlFor="email">
                メールアドレス
              </label>
              <input
                type="email"
                id="email"
                name="email"
                className="mt-2 p-2 w-full border"
              />
            </div>
            <div className="mb-4">
              <label className="block font-semibold" htmlFor="password">
                パスワード
              </label>
              <input
                type="password"
                id="password"
                name="password"
                className="mt-2 p-2 w-full border"
              />
            </div>
            <div className="mb-4">
              <label className="block font-semibold" htmlFor="confirmPassword">
                パスワード確認
              </label>
              <input
                type="password"
                id="confirmPassword"
                name="confirmPassword"
                className="mt-2 p-2 w-full border"
              />
            </div>
            <button
              type="submit"
              className="mt-4 px-4 py-2 bg-green-500 text-white font-bold"
            >
              登録
            </button>
          </form>
        </div>
        <h2 className="text-xl font-bold mt-8">お問い合わせ一覧</h2>
        <table className="min-w-full mt-4 border-collapse border border-gray-200">
          <thead>
            <tr>
              <th className="border border-gray-300 px-4 py-2">編集</th>
              <th className="border border-gray-300 px-4 py-2">ステータス</th>
              <th className="border border-gray-300 px-4 py-2">名前</th>
              <th className="border border-gray-300 px-4 py-2">メール</th>
              <th className="border border-gray-300 px-4 py-2">
                メッセージ内容
              </th>
            </tr>
          </thead>
          <tbody>
            {inquiries.map((inquiry) => (
              <tr key={inquiry.id}>
                <td className="border border-gray-300 px-4 py-2 text-center">
                  <button
                    className="px-4 py-2 bg-blue-500 text-white font-bold"
                    onClick={() => handleEditClick(inquiry)}
                  >
                    編集
                  </button>
                </td>
                <td className="border border-gray-300 px-4 py-2 text-center">
                  <select
                    name="status"
                    className="p-2 border"
                    value={inquiry.status}
                    onChange={(e) => {
                      const newStatus = e.target.value;
                      setInquiries(
                        inquiries.map((i) =>
                          i.id === inquiry.id ? { ...i, status: newStatus } : i
                        )
                      );
                    }}
                  >
                    <option value="未対応">未対応</option>
                    <option value="対応中">対応中</option>
                    <option value="対応済">対応済</option>
                  </select>
                </td>
                <td className="border border-gray-300 px-4 py-2">
                  {inquiry.name}
                </td>
                <td className="border border-gray-300 px-4 py-2">
                  {inquiry.email}
               

 </td>
                <td className="border border-gray-300 px-4 py-2">
                  {inquiry.message}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
        <h2 className="text-xl font-bold mt-8">新規お問い合わせを投稿する</h2>
        <form onSubmit={handleNewInquirySubmit} className="mt-4">
          <div className="mb-4">
            <label className="block font-semibold" htmlFor="name">
              名前
            </label>
            <input
              type="text"
              id="name"
              name="name"
              className="mt-2 p-2 w-full border"
              value={newInquiry.name}
              onChange={handleNewInquiryChange}
            />
          </div>
          <div className="mb-4">
            <label className="block font-semibold" htmlFor="email">
              メール
            </label>
            <input
              type="email"
              id="email"
              name="email"
              className="mt-2 p-2 w-full border"
              value={newInquiry.email}
              onChange={handleNewInquiryChange}
            />
          </div>
          <div className="mb-4">
            <label className="block font-semibold" htmlFor="message">
              メッセージ内容
            </label>
            <textarea
              id="message"
              name="message"
              className="mt-2 p-2 w-full border"
              value={newInquiry.message}
              onChange={handleNewInquiryChange}
            ></textarea>
          </div>
          <button
            type="submit"
            className="mt-4 px-4 py-2 bg-green-500 text-white font-bold"
          >
            送信
          </button>
        </form>
      </div>
    </div>
  );
}

export default MainComponent;


詳細解説

インポートと状態管理

use client 指示子は、クライアントサイドで実行するためのモジュールであることを示します。

"use client";
import React from "react";

React フックを使用して、状態を管理します。inquiries は問い合わせリスト、selectedInquiry は選択された問い合わせ、status は問い合わせのステータス、newInquiry は新規問い合わせの情報です。

function MainComponent() {
const [inquiries, setInquiries] = React.useState([
{
id: 1,
name: "Taro Yamada",
email: "taro@example.com",
message: "お問い合わせ内容1",
status: "未対応",
},
{
id: 2,
name: "Hanako Tanaka",
email: "hanako@example.com",
message: "お問い合わせ内容2",
status: "対応中",
},
]);
const [selectedInquiry, setSelectedInquiry] = React.useState(null);
const [status, setStatus] = React.useState("");
const [newInquiry, setNewInquiry] = React.useState({
name: "",
email: "",
message: "",
});


問合せの編集部分

handleEditClick は問い合わせを選択し、編集モードに切り替える関数です。

  const handleEditClick = (inquiry) => {
setSelectedInquiry(inquiry);
setStatus(inquiry.status);
};

handleStatusChange はステータスを変更するための関数です。

  const handleStatusChange = (event) => {
setStatus(event.target.value);
};

handleSave は変更を保存し、問い合わせリストを更新する関数です。

  const handleSave = () => {
setInquiries(
inquiries.map((inquiry) =>
inquiry.id === selectedInquiry.id
? { ...inquiry, status: status }
: inquiry
)
);
setSelectedInquiry(null);
};


新規お問い合わせの編集

handleNewInquiryChange は新規問い合わせの入力値を管理する関数です。

  const handleNewInquiryChange = (event) => {
const { name, value } = event.target;
setNewInquiry({ ...newInquiry, [name]: value });
};

handleNewInquirySubmit は新規問い合わせをリストに追加するための関数です。

  const handleNewInquirySubmit = (event) => {
event.preventDefault();
setInquiries([
...inquiries,
{ ...newInquiry, id: inquiries.length + 1, status: "未対応" },
]);
setNewInquiry({ name: "", email: "", message: "" });
};


ページスクロールの処理

useEffect フックを使用して、ページのハッシュが変更されたときに特定の要素にスクロールする処理を追加しています。

  React.useEffect(() => {
const handleHashChange = () => {
if (window.location.hash === "#register") {
document
.getElementById("register-page")
.scrollIntoView({ behavior: "smooth" });
}
};
window.addEventListener("hashchange", handleHashChange);
return () => {
  window.removeEventListener("hashchange", handleHashChange);
};
  }, []);


UIのレンダリング

selectedInquiry がある場合、編集画面を表示します。

if (selectedInquiry) {
return (
<div className="p-6">
<h2 className="text-xl font-bold">お問い合わせ編集</h2>
<div className="mt-4">
<p className="font-semibold">名前: {selectedInquiry.name}</p>
<p className="font-semibold">メール: {selectedInquiry.email}</p>
<p className="font-semibold">
メッセージ内容: {selectedInquiry.message}
</p>
<div className="mt-4">
<label className="font-semibold" htmlFor="status">
ステータス
</label>
<select
id="status"
name="status"
className="ml-2 p-2 border"
value={status}
onChange={handleStatusChange}
>
<option value="未対応">未対応</option>
<option value="対応中">対応中</option>
<option value="対応済">対応済</option>
</select>
</div>
<button
className="mt-4 px-4 py-2 bg-green-500 text-white font-bold"
onClick={handleSave}
>
保存
</button>
</div>
</div>
);
}


メニューと問い合わせ一覧の表示

メニューや問い合わせ一覧、会員登録フォームを含むメインの UI をレンダリングしています。

  return (
<div className="flex flex-col lg:flex-row">
<div className="w-full lg:w-[300px] bg-gray-800 text-white">
<div className="p-4 text-lg font-bold">Menu</div>
<ul>
<li className="p-4 hover:bg-gray-700">
<a href="#home" className="font-semibold">
HOME
</a>
</li>
<li className="p-4 hover:bg-gray-700">
<a href="#register" className="font-semibold">
会員登録
</a>
</li>
<li className="p-4 hover:bg-gray-700">
<a href="#inquiries" className="font-semibold">
お問い合せ一覧
</a>
</li>
<li className="p-4 hover:bg-gray-700">
<a href="members.html" target="_blank" className="font-semibold">
会員一覧
</a>
</li>
</ul>
</div>
<div className="flex-grow p-6 overflow-auto">
<div id="register-page">
<h2 className="text-xl font-bold">会員登録</h2>
<form className="mt-4">
<div className="mb-4">
<label className="block font-semibold" htmlFor="username">
ユーザー名
</label>
<input
type="text"
id="username"
name="username"
className="mt-2 p-2 w-full border"
/>
</div>
<div className="mb-4">
<label className="block font-semibold" htmlFor="email">
メールアドレス
</label>
<input
type="email"
id="email"
name="email"
className="mt-2 p-2 w-full border"
/>
</div>
<div className="mb-4">
<label className="block font-semibold" htmlFor="password">
パスワード
</label>
<input
type="password"
id="password"
name="password"
className="mt-2 p-2 w-full border"
/>
</div>
<div className="mb-4">
<label className="block font-semibold" htmlFor="confirmPassword">
パスワード確認
</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
className="mt-2 p-2 w-full border"
/>
</div>
<button
type="submit"
className="mt-4 px-4 py-2 bg-green-500 text-white font-bold"
>
登録
</button>
</form>
</div>
<h2 className="text-xl font-bold mt-8">お問い合わせ一覧</h2>
<table className="min-w-full mt-4 border-collapse border border-gray-200">
<thead>
<tr>
<th className="border border-gray-300 px-4 py-2">編集</th>
<th className="border border-gray-300 px-4 py-2">ステータス</th>
<th className="border border-gray-300 px-4 py-2">名前</th>
<th className="border border-gray-300 px-4 py-2">メール</th>
<th className="border border-gray-300 px-4 py-2">
メッセージ内容
</th>
</tr>
</thead>
<tbody>
{inquiries.map((inquiry) => (
<tr key={inquiry.id}>
<td className="border border-gray-300 px-4 py-2 text-center">
<button
className="px-4 py-2 bg-blue-500 text-white font-bold"
onClick={() => handleEditClick(inquiry)}
>
編集
</button>
</td>
<td className="border border-gray-300 px-4 py-2 text-center">
<select
name="status"
className="p-2 border"
value={inquiry.status}
onChange={(e) => {
const newStatus = e.target.value;
setInquiries(
inquiries.map((i) =>
i.id === inquiry.id ? { ...i, status: newStatus } : i
)
);
}}
>
<option value="未対応">未対応</option>
<option value="対応中">対応中</option>
<option value="対応済">対応済</option>
</select>
</td>
<td className="border border-gray-300 px-4 py-2">
{inquiry.name}
</td>
<td className="border border-gray-300 px-4 py-2">
{inquiry.email}
</td>
<td className="border border-gray-300 px-4 py-2">
{inquiry.message}
</td>
</tr>
))}
</tbody>
</table>
<h2 className="text-xl font-bold mt-8">新規お問い合わせを投稿する</h2>
<form onSubmit={handleNewInquirySubmit} className="mt-4">
<div className="mb-4">
<label className="block font-semibold" htmlFor="name">
名前
</label>
<input
type="text"
id="name"
name="name"
className="mt-2 p-2 w-full border"
value={newInquiry.name}
onChange={handleNewInquiryChange}
/>
</div>
<div className="mb-4">
<label className="block font-semibold" htmlFor="email">
メール
</label>
<input
type="email"
id="email"
name="email"
className="mt-2 p-2 w-full border"
value={newInquiry.email}
onChange={handleNewInquiryChange}
/>
</div>
<div className="mb-4">
<label className="block font-semibold" htmlFor="message">
メッセージ内容
</label>
<textarea
id="message"
name="message"
className="mt-2 p-2 w-full border"
value={newInquiry.message}
onChange={handleNewInquiryChange}
></textarea>
</div>
<button
type="submit"
className="mt-4 px-4 py-2 bg-green-500 text-white font-bold"
>
送信
</button>
</form>
</div>
</div>
);
}

export default MainComponent;


まとめ

このコードは、React と TailwindCSS を使用してシンプルな問い合わせ管理システムを提供します。各問い合わせのステータス管理や新規問い合わせの追加機能を含んでおり、状態管理のための React フックを活用しています。

この記事が気に入ったらサポートをしてみませんか?