LoginSignup
1
0

ReactとJavaScriptで問い合わせフォームの作成してみた!その4

Last updated at Posted at 2024-06-21

はじめに

こんにちは!WEBエンジニア転職を目指しているK.Yです!
今回は、お問い合わせフォームを実装してみました!
以下、完成したお問い合わせフォームです!

fuga.gif

ブログ作成の過程を以下で、それぞれ纏めましたので、
良かったら見てください:sunglasses:

重複している部分は省略しております!

対象者

・ React初心者
・ フロントエンドに興味がある人

バージョン

"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1"

JavaScript ES6以降

コード

PostsList.js

import React, { useEffect, useState } from 'react'
import './App.css'
import { Link } from 'react-router-dom';
const PostsList = () => {

  const formatDate = (dateString) => {
    const date = new Date(dateString);
    const options = { year: 'numeric', month: 'numeric', day: 'numeric' };

    return date.toLocaleDateString('ja-JP', options);
  };
  const [posts, setPosts] = useState({ articles: [] });
  const [loading, setLoading] = useState(true); 
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch("https://1hmfpsvto6.execute-api.ap-northeast-1.amazonaws.com/dev/posts");
        const data = await response.json();
        
        setPosts( data );
      }  finally {
        setLoading(false); 
      }
    };
    fetchData();
  }, []);
  if (loading) {
    return <div>Loading...</div>; 
  }
  return (
    <div className="App">
      <header className="header-App">
        <Link className="link" to="/">Blog</Link>
        <Link className="link" to="/inquiry">お問い合わせ</Link>
      </header>

      {
        Array.isArray(posts.posts) && posts.posts.map(article => (
          <div key={article.id} className="posts-info">
            <ul >
              <li>
                <Link to={`/post/${article.id}`}>
                  <div className="date">{formatDate(article.createdAt)}</div>
                  <div className="programming-language">{article.categories.map((category, idx) => (
                    <span key={idx} className="category-box">{category}</span>
                  ))}</div>
                  <div className="title">{article.title}</div>
                  <div className="content" dangerouslySetInnerHTML={{ __html: article.content }}>
                  </div>
                </Link>
              </li>
            </ul>
          </div>
        ))}
    </div>
  );
}
export default PostsList;
DetailsPage.js

mport { Link, useParams } from 'react-router-dom';
import React, { useEffect, useState } from 'react'
import './App.css';
const DetailsPage = () => {
  const { id } = useParams();
  const formatDate = (dateString) => {
    const date = new Date(dateString);
    const options = { year: 'numeric', month: 'numeric', day: 'numeric' };
    return date.toLocaleDateString('ja-JP', options);
  }
  const [detailsData, setDetailsData] = useState();
  const [loading, setLoading] = useState(true); 
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(`https://1hmfpsvto6.execute-api.ap-northeast-1.amazonaws.com/dev/posts/${id}`);
        const result = await response.json();
        setDetailsData(result.post); 
      } finally {
        setLoading(false); 
      }
    };
    fetchData();
  }, [id]);
  if (loading) {
    return <div>Loading...</div>; 
  }
  if (!detailsData) return <div>投稿が見つかりません</div>;
  return (
    <div className='App'>
      <header className="header-App">
        <Link to="/" className="link">Blog</Link>
        <Link to="/inquiry" className="link" >お問い合わせ</Link>
      </header>

        <div style={{ border: 'none' }} className="posts-info">
          <ul>
            <li key={detailsData.id}>
              <div><img src={detailsData.thumbnailUrl} alt="img" /></div>
              <div className="date">{formatDate(detailsData.createdAt)}</div>
              <div className="programming-language">
                {detailsData.categories.map((category, idx) => (
                  <span key={idx} className="category-box">{category}</span>
                ))}
              </div>
              <div className="title">{detailsData.title}</div>
              <div style={{ display: 'block' }} className="content" dangerouslySetInnerHTML={{ __html: detailsData.content }}></div>
            </li>
          </ul>
        </div>
    </div>
  );
}
export default DetailsPage;
InquiryPage.js


import { Link } from 'react-router-dom';
import React, { useState } from 'react';
import './App.css';
const InquiryPage = () => {
  const [inquiryData, setInquiryData] = useState({ name: '', email: '', message: '' });
  const [errors, setErrors] = useState({});
  const [isSubmitting, setIsSubmitting] = useState(false);
  const handleChange = (e) => {
    const { id, value } = e.target;
    setInquiryData((prevData) => ({ ...prevData, [id]: value }));
  };
  const validate = () => {
    const tempErrors = {};
    if (!inquiryData.name) tempErrors.name = 'お名前は必須です。';
    if (!inquiryData.email) tempErrors.email = 'メールアドレスは必須です。';
    if (!inquiryData.message) tempErrors.message = '本文は必須です。';
    setErrors(tempErrors);
    return Object.keys(tempErrors).length === 0;
  };
  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!validate()) return;
    setIsSubmitting(true);
    try {
      const response = await fetch("https://1hmfpsvto6.execute-api.ap-northeast-1.amazonaws.com/dev/contacts", {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(inquiryData),
      });
      if (!response.ok) throw new Error('Network response was not ok');
      alert('送信しました');
      setInquiryData({ name: '', email: '', message: '' });
      setErrors({});
    } catch (error) {
      console.error('Error submitting form:', error);
    } finally {
      setIsSubmitting(false);
    }
  };

  const handleClear = () => {
    setInquiryData({name: '', email: '', message: ''});
  }
  return (
    <div className='App'>
      <header className="header-App">
        <Link to="/" className="link">Blog</Link>
        <Link to="/inquiry" className="link">お問い合わせ</Link>
      </header>
      <div className="inquiry">
        <h1>問合わせフォーム</h1>
        <form id="myForm" onSubmit={handleSubmit}>
          <div className="formItem">
              <label>
              <dl>
                <dt>お名前</dt>
                <div className="text">
                  <dd>
                    <input
                    type="text"
                    id="name"
                    maxLength="30"
                    value={inquiryData.name}
                    onChange={handleChange}
                    disabled={isSubmitting}
                  />
                  </dd>
                  {errors.name && <span>{errors.name}</span>}
                </div>
                </dl>
                </label>
                
            <div className="label">
              <label>
                <dl>
                <dt>メールアドレス</dt>
                <div className="text">
                  <dd>
                    <input
                    type="text"
                    id="email"
                    value={inquiryData.email}
                    onChange={handleChange}
                    disabled={isSubmitting}
                  />
                  </dd>
                  {errors.email && <span>{errors.email}</span>}
                </div>
                </dl>
                </label>
            </div>
            <div className="label">
              <label>
                <dl>
                  <dt>本文</dt>
              <div className="text">
              <dd>
                <textarea
                  type="text"
                  id="message"
                  maxLength="500"
                  value={inquiryData.message}
                  onChange={handleChange}
                  disabled={isSubmitting}
                  rows="10"
                />
                </dd>
                {errors.message && <span>{errors.message}</span>}
              </div>
              </dl>
              </label>
            </div>
          </div>
          <div className="btn">
          <input type="submit" value="送信" disabled={isSubmitting} />
          <input type="reset" value="クリア" onClick={handleClear} disabled={isSubmitting} />
          </div>
        </form>
      </div>
    </div>
  );
};
export default InquiryPage;
App.js

import React from 'react';
import './App.css';
import { Routes, Route } from 'react-router-dom';
import PostsList from './PostsList';
import DetailsPage from './DetailsPage';
import InquiryPage from './InquiryPage';
const App = () => {
  return (
    <Routes>
      <Route path="/" element={<PostsList />} />
      <Route path="/post/:id" element={<DetailsPage  />} />
      <Route path="/inquiry" element={<InquiryPage />} />
    </Routes>
  );

InquiryPage.js (お問い合わせフォーム)

const [inquiryData, setInquiryData] = useState({ name: '', email: '', message: '' });
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);

inquiryDatasetInquiryDataは、フォームの入力データを管理するためのステート。
useStateの引数として渡されるオブジェクト{ name: '', email: '', message: '' }は、ステートの初期値です。
それぞれ空文字列で設定。

errorssetErrorsは、フォームのバリデーションエラーメッセージを管理するためのステートです。

入力変更ハンドラー

const handleChange = (e) => {
  const { id, value } = e.target;
  setInquiryData((prevData) => ({ ...prevData, [id]: value }));
};

handleChange関数は、フォームの入力が変更されたときに呼び出されます。

e.target.idは変更された入力フィールドのIDを取得します。

e.target.valueは変更された入力フィールドの値を取得します。

setInquiryDataを使って、inquiryDataステートを更新します。

・スプレッド構文...を使って、prevDataオブジェクトの全てのプロパティを新しいオブジェクトにコピーします。
これにより、既存のステートを保持しつつ、一部のプロパティだけを更新することができます。

バリデーション関数

const validate = () => {
  const tempErrors = {};
  if (!inquiryData.name) tempErrors.name = 'お名前は必須です。';
  if (!inquiryData.email) tempErrors.email = 'メールアドレスは必須です。';
  if (!inquiryData.message) tempErrors.message = '本文は必須です。';
  setErrors(tempErrors);
  return Object.keys(tempErrors).length === 0;
};

validate関数は、フォームの入力データを検証します。

inquiryDataの各フィールドが空である場合、エラーメッセージをtempErrorsオブジェクトに追加。

setErrorsを使って、errorsステートを更新します。

tempErrorsオブジェクトが空であればtrueを返し、そうでなければfalseを返す。

フォーム送信ハンドラー

const handleSubmit = async (e) => {
  e.preventDefault();
  if (!validate()) return;
  setIsSubmitting(true);
  try {
    const response = await fetch("https://1hmfpsvto6.execute-api.ap-northeast-1.amazonaws.com/dev/contacts", {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(inquiryData),
    });
    if (!response.ok) throw new Error('Network response was not ok');
    alert('送信しました');
    setInquiryData({ name: '', email: '', message: '' });
    setErrors({});
  } catch (error) {
    console.error('Error submitting form:', error);
  } finally {
    setIsSubmitting(false);
  }
};

handleSubmit関数は、フォームが送信されたときに呼び出される。

e.preventDefault()を使って、フォームのデフォルトの送信動作を防ぐ。

validate関数を呼び出して、入力データが有効でない場合は処理を中断。

setIsSubmitting(true)を使って、フォームが送信中であることを示す。

fetch関数を使って、APIにデータを送信します。

method: POSTはHTTPメソッドをPOSTに設定します。

headers: { 'Content-Type': 'application/json' }はリクエストヘッダーを設定します。

body: JSON.stringify(inquiryData)はリクエストボディにJSON形式のデータを設定します。

setErrorsは、フォームのバリデーションエラーを管理するためのステート更新関数です。

setErrors({})は、エラーオブジェクトを空にリセットします。これにより、前回の送信時に発生したエラーメッセージがクリアされます。

・ catchブロックは、tryブロック内で発生したエラーをキャッチして処理。
catch (error)errorは、発生したエラーオブジェクトを指します。
console.error('Error submitting form:', error)は、エラーメッセージをコンソールに出 力します。これにより、デバッグやログの確認が容易になります。

finallyブロックは、tryブロックが成功しても失敗しても必ず実行されます。
finallyブロック内で行われる処理は、リソースの解放やクリーンアップ操作などです。
setIsSubmitting(false)は、フォームの送信状態を解除します。これにより、送信ボタンが再び有効になります。

フォームクリアハンドラー

const handleClear = () => {
  setInquiryData({ name: '', email: '', message: '' });
};

handleClear関数は、フォームの各入力データをクリアします。

JSXのレンダリング

return (
  <div className='App'>
    <header className="header-App">
      <Link to="/" className="link">Blog</Link>
      <Link to="/inquiry" className="link">お問い合わせ</Link>
    </header>
    <div className="inquiry">
      <h1>問合わせフォーム</h1>
      <form id="myForm" onSubmit={handleSubmit}>
        <div className="formItem">
          <label>
            <dl>
              <dt>お名前</dt>
              <div className="text">
                <dd>
                  <input
                    type="text"
                    id="name"
                    maxLength="30"
                    value={inquiryData.name}
                    onChange={handleChange}
                    disabled={isSubmitting}
                  />
                </dd>
                {errors.name && <span>{errors.name}</span>}
              </div>
            </dl>
          </label>
          <div className="label">
            <label>
              <dl>
                <dt>メールアドレス</dt>
                <div className="text">
                  <dd>
                    <input
                      type="text"
                      id="email"
                      value={inquiryData.email}
                      onChange={handleChange}
                      disabled={isSubmitting}
                    />
                  </dd>
                  {errors.email && <span>{errors.email}</span>}
                </div>
              </dl>
            </label>
          </div>
          <div className="label">
            <label>
              <dl>
                <dt>本文</dt>
                <div className="text">
                  <dd>
                    <textarea
                      type="text"
                      id="message"
                      maxLength="500"
                      value={inquiryData.message}
                      onChange={handleChange}
                      disabled={isSubmitting}
                      rows="10"
                    />
                  </dd>
                  {errors.message && <span>{errors.message}</span>}
                </div>
              </dl>
            </label>
          </div>
        </div>
        <div className="btn">
          <input type="submit" value="送信" disabled={isSubmitting} />
          <input type="reset" value="クリア" onClick={handleClear} disabled={isSubmitting} />
        </div>
      </form>
    </div>
  </div>
);

フォームの定義

<form id="myForm" onSubmit={handleSubmit}>
  ...
</form>

<form>タグは、フォーム全体を定義。
id="myForm"はフォームのIDを設定します。
onSubmit={handleSubmit}は、フォームが送信されたときにhandleSubmit関数を呼び出す。

フォームの各フィールド(お名前フィールド)

<div className="formItem">
  <label>
    <dl>
      <dt>お名前</dt>
      <div className="text">
        <dd>
          <input
            type="text"
            id="name"
            maxLength="30"
            value={inquiryData.name}
            onChange={handleChange}
            disabled={isSubmitting}
          />
        </dd>
        {errors.name && <span>{errors.name}</span>}
      </div>
    </dl>
  </label>
</div>

<label>タグは、フォームフィールドのラベルを定義

<dl>タグは、定義リストを作成。

<dt>タグは用語(この場合は「お名前」)を定義。

<div className="text">は、入力フィールドを囲むコンテナです。

<dd>タグは定義の説明部分を定義。

<input>タグは、テキスト入力フィールドを定義。

type="text"は、テキスト入力フィールドであることを示します。

id="name"は、フィールドのIDを設定。

maxLength="30"は、入力できる文字数の最大値を設定。

value={inquiryData.name}は、フィールドの値をinquiryData.nameにバインドします。

onChange={handleChange}は、入力が変更されたときにhandleChange関数を呼び出す。

disabled={isSubmitting}は、フォームが送信中の場合にフィールドを無効化。

{errors.name && <span>{errors.name}</span>}は、errors.nameが存在する場合にエラーメッセージを表示。

メールアドレスフィールド

<div className="label">
  <label>
    <dl>
      <dt>メールアドレス</dt>
      <div className="text">
        <dd>
          <input
            type="text"
            id="email"
            value={inquiryData.email}
            onChange={handleChange}
            disabled={isSubmitting}
          />
        </dd>
        {errors.email && <span>{errors.email}</span>}
      </div>
    </dl>
  </label>
</div>

・メールアドレスフィールドは、お名前フィールドと同様の構造を持ちますが、idvalueemailに変更されています。

本文フィールド

<div className="label">
  <label>
    <dl>
      <dt>本文</dt>
      <div className="text">
        <dd>
          <textarea
            type="text"
            id="message"
            maxLength="500"
            value={inquiryData.message}
            onChange={handleChange}
            disabled={isSubmitting}
            rows="10"
          />
        </dd>
        {errors.message && <span>{errors.message}</span>}
      </div>
    </dl>
  </label>
</div>

・本文フィールドも同様の構造を持ちますが、<textarea>タグを使用して複数行のテキスト入力を可能にしています。
rows="10"は、テキストエリアの行数を設定します。

送信ボタンとクリアボタン

<div className="btn">
 <input type="submit" value="送信" disabled={isSubmitting} />
 <input type="reset" value="クリア" onClick={handleClear} disabled={isSubmitting} />
</div>

<input type="submit">は、フォームを送信するボタンです。

value="送信"は、ボタンの表示テキストを設定。

disabled={isSubmitting}は、フォームが送信中の場合にボタンを無効化します。

<input type="reset">は、フォームをリセットするボタンです。

value="クリア"は、ボタンの表示テキストを設定します。

onClick={handleClear}は、ボタンがクリックされたときにhandleClear関数を呼び出す。

disabled={isSubmitting}は、フォームが送信中の場合にボタンを無効化します。

ポイント

イベントハンドリング
Reactでは、イベントハンドラーを使ってユーザーの入力や操作に応じた処理を行います。
例) 項目: 入力変更ハンドラー・フォーム送信ハンドラー・フォームクリアハンドラー

フォームのバリデーション
入力の検証:入力データを検証し、エラーメッセージを表示。
例) 項目: バリデーション関数

おわり

今回は、お問い合わせフォームを実装してみました!
React/JavaScriptの基礎を勉強して、アウトプット用に簡易的なブログ作ってみました!
まだまだ理解は浅いですが、コツコツと頑張っていきます!
今回の記事が少しでもお役に立てたら嬉しいです!

1
0
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
1
0