先週書いたHTML5 Form Validationのことを思い出したのでMUIで使ってみたですが、小さな(?)バグがあったので修正してみました。
本当の事を書くと、先週ブログを書く前にこのバグはわかっていたのですが、この修正をしてしまうと HTML5 Form Validation(Client-side validation)の基本から少しずれるかなと考え、そのまま書きました。
バグの再現
バグの再現画面をアニメーションGIFにしてました。
- フォームな何も入力せずREGISTERボタンを押すと、Email入力欄に「このフィールドを入力してください。」とエラーが表示される
- そこでEmail入力欄にxxと入力するとエラーメッセージが「指定された形式で入力してください。」にかわる
- さらにxx@zzと入力するとエラーメッセージが消える、ただし入力欄はエラー状態(赤字)のまま
このプログラムではバリデーションはREGISTERボタンを押した時にに行われますが、HTML5 Form Validationはリアルタイム(キー入力が行われた)にバリデーションが行われるのでこの現象が起きます。
下に先週のコードを置きました。
- ① REGISTERボタンが押されformValidation関数が実行されるとエラーが検出され、ReactのemailErrorステートがtrueになります
- ② emailErrorステートがtrueの時は、Email入力欄のHTML5 ValidationのエラーメッセージがMUIコンポーネント(React)に結び付けるているのでリアルタイムでのバリデーション結果が表示されてしまいます
- また、HTML5 Form Validationが正常になってもReactのステータスが更新されないので、エラー状態(赤字)のままになってしまいます
先週のコード
const formValidation = (): boolean => {
・・・
const e = emailRef?.current;
if (e) {
const ok = e.validity.valid;
setEmailError(!ok); // ← ①
valid &&= ok;
}
・・・
<TextField
margin="normal"
fullWidth
required
inputRef={emailRef}
value={emailValue}
error={emailError}
helperText={emailError && emailRef?.current?.validationMessage} // ← ②
inputProps={ {required: true, pattern: EmailVaildPattern} }
onChange={(e: OnChangeEvent) => setEmailValue(e.target.value)}
label="Email"
/>
サンプルコード
さてバグの原因は上の②なので
- ③ エラーメッセージをステート(state)に持ちます
- ④ エラーが検出された時点でのバリデーション・エラーメッセージがステートの保存されます
- ⑤ エラーの有無はエラーメッセージの有無をTextFieldコンポーネントに渡す
- ⑥ エラーメッセージはそのままTextFieldコンポーネントに渡せます
import { Button, Container, TextField } from "@mui/material";
import React, { useRef, useState } from "react";
type OnChangeEvent = React.ChangeEvent<HTMLInputElement>;
const EmailVaildPattern =
"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)*$";
const App = () => {
const emailRef = useRef<HTMLInputElement>(null);
const passwordRef = useRef<HTMLInputElement>(null);
const confirmPasswordRef = useRef<HTMLInputElement>(null);
const [emailValue, setEmailValue] = useState("");
const [passwordValue, setPasswordValue] = useState("");
const [confirmPasswordValue, setConfirmPasswordValue] = useState("");
const [emailError, setEmailError] = useState(""); // ← ③
const [passwordError, setPasswordError] = useState("");
const [confirmPasswordError, setConfirmPasswordError] = useState("");
const formValidation = (): boolean => {
let valid = true;
const e = emailRef?.current;
if (e) {
const ok = e.validity.valid;
setEmailError(ok ? "" : e.validationMessage); // ← ④
valid &&= ok;
}
const p = passwordRef?.current;
if (p) {
const ok = p.validity.valid;
setPasswordError(ok ? "" : p.validationMessage);
valid &&= ok;
}
const c = confirmPasswordRef?.current;
if (c) {
if (confirmPasswordValue.length > 0 && passwordValue !== confirmPasswordValue) {
c.setCustomValidity("パスワードが一致しません");
} else {
c.setCustomValidity("");
}
const ok = c.validity.valid;
setConfirmPasswordError(ok ? "" : c.validationMessage);
valid &&= ok;
}
return valid;
};
return (
<Container component="main" maxWidth="xs">
<TextField
margin="normal"
fullWidth
required
inputRef={emailRef}
value={emailValue}
error={emailError !== ""} // ← ⑤
helperText={emailError} // ← ⑥
inputProps={{ required: true, pattern: EmailVaildPattern }}
onChange={(e: OnChangeEvent) => setEmailValue(e.target.value)}
label="Email"
/>
<TextField
margin="normal"
fullWidth
required
type="password"
inputRef={passwordRef}
value={passwordValue}
error={passwordError !== ""}
helperText={passwordError}
inputProps={{ required: true }}
onChange={(e: OnChangeEvent) => setPasswordValue(e.target.value)}
label="Password"
/>
<TextField
margin="normal"
fullWidth
required
type="password"
inputRef={confirmPasswordRef}
value={confirmPasswordValue}
error={confirmPasswordError !== ""}
helperText={confirmPasswordError}
inputProps={{ required: true }}
onChange={(e: OnChangeEvent) => setConfirmPasswordValue(e.target.value)}
label="Confirm password"
/>
<Button
variant="contained"
fullWidth
sx={{ mt: 3 }}
onClick={() => {
if (formValidation()) {
alert("OK!");
}
}}
>
Register
</Button>
</Container>
);
};
export default App;
まとめ
HTML5 Form Validationはリアルタイムに行われるので、このコードのようにボタンを押したときだけ実行されるコードではなく、素直にリアルタイム・バリデーションにした方が良いのかも知れません。
しかし私は、リアルタイム・バリデーションはウザい感じがて好きになれないのです😅