Skip to content

Instantly share code, notes, and snippets.

@sheepla
Last active June 15, 2025 13:03
Show Gist options
  • Select an option

  • Save sheepla/469bc23ef5eb46152a69353072253997 to your computer and use it in GitHub Desktop.

Select an option

Save sheepla/469bc23ef5eb46152a69353072253997 to your computer and use it in GitHub Desktop.
MUI + React Hook Formを使ったお問い合わせフォームの実装
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>
</>
);
}
@sheepla

sheepla commented Jun 15, 2025

Copy link
Copy Markdown
Author

実際の画面

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment