Last active
June 15, 2025 13:03
-
-
Save sheepla/469bc23ef5eb46152a69353072253997 to your computer and use it in GitHub Desktop.
MUI + React Hook Formを使ったお問い合わせフォームの実装
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState } from "react"; | |
import { Controller, useForm } from "react-hook-form"; | |
import { | |
Alert, | |
Button, | |
Checkbox, | |
CircularProgress, | |
FormControlLabel, | |
MenuItem, | |
Snackbar, | |
TextField, | |
Typography, | |
} from "@mui/material"; | |
type FormValues = { | |
name: string; | |
email: string; | |
age: number; | |
topic: string; | |
message: string; | |
agree: boolean; | |
}; | |
const sleep = async (delay: number) => await new Promise(resolve => setTimeout(resolve, delay)) | |
export default function ContactForm() { | |
const [loading, setLoading] = useState(false); | |
const [snackbarOpen, setSnackbarOpen] = useState(false) | |
const { control, handleSubmit, formState: { errors }, reset } = useForm< | |
FormValues | |
>({ | |
defaultValues: { | |
name: "", | |
email: "", | |
age: 0, | |
topic: "", | |
message: "", | |
agree: false, | |
}, | |
}); | |
const onSubmit = async (data: FormValues) => { | |
console.log("送信データ:", data); | |
setLoading(true) | |
// スリープして擬似的に送信処理をシミュレート | |
await sleep(1000) | |
setLoading(false); | |
setSnackbarOpen(true); | |
reset(); // フォームをリセット | |
}; | |
return ( | |
<> | |
<form | |
onSubmit={handleSubmit(onSubmit)} | |
style={{ maxWidth: 500, width: "100%" }} | |
> | |
<Controller | |
name="name" | |
control={control} | |
rules={{ | |
required: "お名前は必須です", | |
minLength: { value: 2, message: "2文字以上で入力してください" }, | |
}} | |
render={({ field }) => ( | |
<TextField | |
size="small" | |
{...field} | |
label="お名前" | |
fullWidth | |
margin="normal" | |
error={!!errors.name} | |
helperText={errors.name?.message} | |
/> | |
)} | |
/> | |
<Controller | |
name="email" | |
control={control} | |
rules={{ | |
required: "メールアドレスは必須です", | |
pattern: { | |
value: /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/, | |
message: "メールアドレスの形式が正しくありません", | |
}, | |
}} | |
render={({ field }) => ( | |
<TextField | |
size="small" | |
{...field} | |
label="メールアドレス" | |
fullWidth | |
margin="normal" | |
error={!!errors.email} | |
helperText={errors.email?.message} | |
/> | |
)} | |
/> | |
<Controller | |
name="age" | |
control={control} | |
rules={{ | |
required: "年齢は必須です", | |
min: { value: 18, message: "18歳以上である必要があります" }, | |
max: { value: 120, message: "正しい年齢を入力してください" }, | |
}} | |
render={({ field }) => ( | |
<TextField | |
size="small" | |
{...field} | |
label="年齢" | |
type="number" | |
fullWidth | |
margin="normal" | |
error={!!errors.age} | |
helperText={errors.age?.message} | |
/> | |
)} | |
/> | |
<Controller | |
name="topic" | |
control={control} | |
rules={{ required: "お問い合わせ種別を選択してください" }} | |
render={({ field }) => ( | |
<TextField | |
size="small" | |
{...field} | |
select | |
label="お問い合わせ種別" | |
fullWidth | |
margin="normal" | |
error={!!errors.topic} | |
helperText={errors.topic?.message} | |
> | |
<MenuItem value="support">サポート</MenuItem> | |
<MenuItem value="feedback">フィードバック</MenuItem> | |
<MenuItem value="other">その他</MenuItem> | |
</TextField> | |
)} | |
/> | |
<Controller | |
name="message" | |
control={control} | |
rules={{ | |
required: "お問い合わせ内容は必須です", | |
minLength: { value: 10, message: "10文字以上入力してください" }, | |
}} | |
render={({ field }) => ( | |
<TextField | |
size="small" | |
{...field} | |
label="お問い合わせ内容" | |
multiline | |
rows={4} | |
fullWidth | |
margin="normal" | |
error={!!errors.message} | |
helperText={errors.message?.message} | |
/> | |
)} | |
/> | |
<Controller | |
name="agree" | |
control={control} | |
rules={{ validate: (value) => value || "規約に同意する必要があります" }} | |
render={({ field }) => ( | |
<FormControlLabel | |
control={<Checkbox {...field} checked={field.value} />} | |
label="規約に同意します" | |
/> | |
)} | |
/> | |
{errors.agree && ( | |
<Typography sx={{ color: "error", margin: 0 }}>{errors.agree.message}</Typography> | |
)} | |
<Button | |
type="submit" | |
variant="contained" | |
fullWidth | |
sx={{ mt: 2, position: 'relative' }} | |
disabled={loading} | |
> | |
{loading && ( | |
<CircularProgress | |
size={24} | |
sx={{ | |
color: 'white', | |
position: 'absolute', | |
top: '50%', | |
left: '50%', | |
marginTop: '-12px', | |
marginLeft: '-12px', | |
}} | |
/> | |
)} | |
{loading ? '送信中...' : '送信'} | |
</Button> | |
</form> | |
<Snackbar | |
open={snackbarOpen} | |
autoHideDuration={3000} | |
onClose={() => setSnackbarOpen(false)} | |
anchorOrigin={{ vertical: 'top', horizontal: 'center' }} | |
> | |
<Alert onClose={() => setSnackbarOpen(false)} severity="success" sx={{ width: '100%' }}> | |
お問い合わせを送信しました! | |
</Alert> | |
</Snackbar> | |
</> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
実際の画面