見出し画像

ReactとTailwindでお問合せフォームを作る

今回はReactとTailwind.cssを使って問合せフォームを作ったので、その実装方法を紹介します。

フォームの完成イメージ

デスクトップでの見た目は下の画像見たいな感じです。

画像1

入力するフィールドとしては、
・苗字
・氏名
・メールアドレス
・お問合せ内容
の四つです。

フォームを入力した状態で送信ボタンを押すと、axiosでバックエンド側にPOSTリクエストが飛ばされ、送信完了のメッセージが表示されます。一つ一つのフィールドにReactのstateを割り当てて入力値を管理します。

スマホでは左の画像を非表示にします。スマホでの完成物のイメージを見たい方や実際の動きを確認したい方はこちらで確認してください。(フォームの送信はしないでください。僕に通知が飛んできます。)
僕が運用しているBoxifulというキックボクシングゲームのお問合せフォーム画面です。

サンプルコード

実装しているコード全体です。
要所の説明はコードの下です。

import { Fragment, useState } from 'react';
import SlideBackground from './backgrounds/SlideBackground';
import questionImage from '../images/question.svg';
import API from '../api';

const ContactForm = () => {
 const [isSubmitted, setIsSubmitted] = useState(false);
 const [lastName, setLastName] = useState('');
 const [firstName, setFirstName] = useState('');
 const [email, setEmail] = useState('');
 const [message, setMessage] = useState('');

 const onChangeLastName = (e: React.ChangeEvent<HTMLInputElement>) => {
   setLastName(e.target.value);
 };

 const onChangeFirstName = (e: React.ChangeEvent<HTMLInputElement>) => {
   setFirstName(e.target.value);
 };

 const onChangeEmail = (e: React.ChangeEvent<HTMLInputElement>) => {
   setEmail(e.target.value);
 };

 const onChangeMessage = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
   setMessage(e.target.value);
 };

 const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
   e.preventDefault();
   if (lastName !== '' &&
   firstName !== '' &&
   email !== '' &&
   message !== ''
   ) {
     // Send post request to backend
     API.post('/contact_form/handle_contact_form/', {
       'last_name': lastName,
       'first_name': firstName,
       email,
       message
     }).then(() => {
       setIsSubmitted(true);
       // reset forms
       setLastName(' ');
       setFirstName(' ');
       setEmail(' ');
       setMessage(' ');
     }).catch((error) => {
       console.log(error);
     })
   }
 };

 return (
   <Fragment>
     <div className="container mx-auto px-5 md:px-10 min-h-screen">
       <h1 className="mt-5 mb-2 ml-5 text-3xl font-bold text-yellow-500">
         Contact Form
       </h1>
       <section className="mb-10">
         <h2 className="text-xl pt-3 mb-3 font-bold">お問合わせ</h2>
         <div className="rounded-lg shadow-xl bg-white p-6">
           <div className="grid grid-cols-1 md:grid-cols-2 gap-5 p-5 items-center">
             <img className="hidden md:block" src={questionImage} alt="質問" />
             <form className="w-full max-w-lg" onSubmit={handleSubmit}>
               <div className="flex flex-wrap -mx-3 mb-6">
                 <div className="w-full md:w-1/2 px-3 mb-6 md:mb-0">
                   <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
                     苗字
                   </label>
                   <input
                     className="appearance-none block w-full bg-gray-200 text-gray-700 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white"
                     type="text"
                     placeholder="山田"
                     value={lastName}
                     onChange={onChangeLastName}
                   />
                   {lastName === '' && (
                     <p className="text-red-500 text-xs italic">
                       苗字を入力してください。
                     </p>
                   )}
                 </div>
                 <div className="w-full md:w-1/2 px-3">
                   <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
                     名前
                   </label>
                   <input
                     className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
                     type="text"
                     placeholder="太郎"
                     value={firstName}
                     onChange={onChangeFirstName}
                   />
                   {firstName === '' && (
                     <p className="text-red-500 text-xs italic">
                       名前を入力してください。
                     </p>
                   )}
                 </div>
               </div>
               <div className="flex flex-wrap -mx-3 mb-6">
                 <div className="w-full px-3">
                   <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
                     メールアドレス
                   </label>
                   <input
                     className="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
                     type="email"
                     placeholder="example@yahoo.co.jp"
                     value={email}
                     onChange={onChangeEmail}
                   />
                   {email === '' && (
                     <p className="text-red-500 text-xs italic">
                       メールアドレスを入力してください。
                     </p>
                   )}
                 </div>
               </div>
               <div className="flex flex-wrap -mx-3 mb-6">
                 <div className="w-full px-3">
                   <label className="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2">
                     お問い合わせ内容
                   </label>
                   <textarea
                     value={message}
                     onChange={onChangeMessage}
                     className=" no-resize appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 mb-3 leading-tight focus:outline-none focus:bg-white focus:border-gray-500 h-48 resize-none"
                   ></textarea>
                   {message === '' && (
                     <p className="text-red-500 text-xs italic">
                       お問合せ内容を入力してください。
                     </p>
                   )}
                 </div>
               </div>
               <div className="md:flex md:items-center mb-5">
                 <div className="md:w-1/3">
                   <button
                     className="shadow bg-yellow-500 hover:bg-yellow-300 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded"
                     type="submit"
                   >
                     送信
                   </button>
                 </div>
               </div>
                 {isSubmitted && (
                   <p className="text-green-400 text-lg text-bold">
                     ありがとうございます。お問合せ内容を受け付けました。
                   </p>
                 )}
             </form>
           </div>
         </div>
       </section>
     </div>
     <SlideBackground />
   </Fragment>
 );
};

export default ContactForm;

importについて

import questionImage from '../images/question.svg';
import API from '../api';

一つ目のimportは画像をインポートしています。もちろんsvg形式ではなく、pngとかjpgでOKです。
二つ目のimportはaxiosのインスタンスをを取得しています。import元はこんな感じです。

import axios from 'axios';

export default axios.create({
 baseURL: 'バックエンドURL',
});

stateの定義

stateでフォームが送信済みかどうかのフラグと、それぞれのインプットの入力値の管理をします。

const [isSubmitted, setIsSubmitted] = useState(false);
const [lastName, setLastName] = useState('');
const [firstName, setFirstName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');

値が変化したら、onChangeでstateの値を変更します。

const onChangeLastName = (e: React.ChangeEvent<HTMLInputElement>) => {
   setLastName(e.target.value);
 };

 const onChangeFirstName = (e: React.ChangeEvent<HTMLInputElement>) => {
   setFirstName(e.target.value);
 };

 const onChangeEmail = (e: React.ChangeEvent<HTMLInputElement>) => {
   setEmail(e.target.value);
 };

 const onChangeMessage = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
   setMessage(e.target.value);
 };

四つの関数を作っていますが、switchとかを使って一つの関数にまとめた方が良いかもしれないです。

入力されているかチェック

{lastName === '' && (
   <p className="text-red-500 text-xs italic">
       苗字を入力してください。
   </p>
)}

何も入力されていない時にエラーメッセージを表示します。これをそれぞれのフォームに追加。

POSTリクエストをバックエンドに送信

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
   e.preventDefault();
   if (lastName !== '' &&
   firstName !== '' &&
   email !== '' &&
   message !== ''
   ) {
     // Send post request to backend
     API.post('/contact_form/handle_contact_form/', {
       'last_name': lastName,
       'first_name': firstName,
       email,
       message
     }).then(() => {
       setIsSubmitted(true);
       // reset forms
       setLastName(' ');
       setFirstName(' ');
       setEmail(' ');
       setMessage(' ');
     }).catch((error) => {
       console.log(error);
     })
   }
 };

全てのインプットが入力されている場合、POSTリクエストをバックエンドに飛ばして、isSubmittedをtrueにします。

送信完了メッセージの表示

POSTリクエストがエラーなく送信されたら、送信完了メッセージを表示します。

{isSubmitted && (
     <p className="text-green-400 text-lg text-bold">
         ありがとうございます。お問合せ内容を受け付けました。
     </p>
)}

サンプルコードは以上です。

Boxifulについて

最後に、僕が運用しているBoxifulというサイトの宣伝をさせてもらいます。Boxifulは実際に体を動かすWEBベースのキックボクシングゲームで、AIを使って、人の体の動きを認識してキックやパンチの判定をしています。

ちなみに、この記事で紹介しているお問合せフォームは、このサイトを作った時に作ったものです。

キックやパンチの判定自体はそこまで正確ではないんですが、PCさえあれば自宅での運動をちょっとだけ楽しくできるので、よかったら試してみてください。

(プレイ画像)

画像2



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