【React】必須項目でもページ表示直後だけは未入力エラーメッセージを表示しない inputの実装
フロントエンドエンジニアの茶木です。
UIデザイナーと、input フォームとエラーメッセージの挙動について話しました。
入力必須の input が未入力なら「入力してください」とエラーメッセージを表示するとします。ページ表示直後は当然未入力なのですが、このタイミングでは、エラーメッセージを表示しないようにしたいとのこと。
これは真っ当な意見で、たとえば、新規登録の記入フォームページを開いて、ほとんどの input に最初から真っ赤な未入力エラーメッセージが表示されていたら、ユーザー視点では嫌ですものね。
未入力の判定は React と相性が悪い
実装しようとすると、React とちょっと相性の悪いものだとわかります。
まず、”空欄” ならエラーメッセージを表示するinput のコード例です。
なお、今回は、
“エラーメッセージは submit 時にまとめて出すのではなくて、入力ごとに出す”
ものとします。文字数制限や、入力可能文字種のエラーはその場で表示された方が、利便性が高いので、未入力の判定もこちらに合わせます。
export default function Index() {
const [text, setText] = useState("");
return (
<TextField
label="名前(必須)"
value={text}
onChange={(e) => setText(e.target.value)}
helperText={text ? "" : "入力してください"}
error={!text}
/>
);
}
TextField
は MUI のものですが、useState
のお手本のようにシンプルに書けます。ですが、実行するとページ表示直後からエラーが表示されます。表示直後は”空欄”なので。
相性が悪いといったのはまさにここです。通常、Reactコンポーネントはステータスと描画の状態が一致します。そのため、最初から空欄でも入力後に消して空欄になったのでも、描画の状態が一致するのは自然なのです。
未入力というステータスを管理する必要がある
というわけで「未入力かどうか」というステータスを持てば、Reactコンポーネントの自然な挙動を保持しつつ、未入力時には空欄であってもエラーメッセージを表示しないという挙動を実現できそうです。
const [touched, setTouched] = useState<string | undefined>();
ただし上記のように単純に useState
を増やすと、onChange
をはじめ、各箇所が複雑になるので、別のアプローチをします。
export default function Index() {
const [text, setText] = useState<string | undefined>();
return (
<TextField
label="名前(必須)"
value={text}
onChange={(e) => setText(e.target.value)}
helperText={text !== undefined && !text ? "入力してください" : ""}
error={text !== undefined && !text}
/>
);
}
text
に undefined
を許可し、初期値を undefined
とします。これを「未入力」とし、""
空文と区別します。これにそって、以下のように空文の判定を書きます。
text !== undefined && !text
空文のときはエラーメッセージを出しますが未入力では出しません。
これにより、表示直後の「未入力」ではエラーメッセージは出ず、入力後に削除したケースでエラーメッセージが表示されるようになります。
「未入力」まわりの TIPS
以下、運用上よくある「未入力」のステータスの取り回しです。
入力の終わりを判定して、未入力メッセージを出す
onBlur={(e) => { setText(e.target.value) }}
入力終わりの判定は onBlur
が良さそうです。onChange
のハンドルと同じように書けば input
の value
で text
ステータスを上書きするので、入力があればそのままに、 undefined
であれば 空文に書き換えられ未入力エラーメッセージが表示されます。
submit 時のバリデーションの書き方
const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!text) {
setText("");
return;
}
hundleSubmit(text);
};
onSubmit
などのタイミングで、実際のエラーメッセージとは別にバリデーションを行う必要があります。この場合は未入力かどうか関係なく、従来どおり text
の評価が false
ならエラーにします。
また、エラーがあれば、text
を undefined
から空文に変えることでエラーメッセージを更新できます。
リセット(入力破棄)の書き方
const reset = () => { setText(undefined) };
入力内容を破棄する場合は、空文ではなく未入力をセットすればOKです。
おわりに
実装面では、エラーやエラーメッセージは、適切なタイミングでユーザーに提示する必要があるため、React のステータスの更新と相性が悪いケースが多々あります。コードの見通しとバランスを取れる形を模索する必要があります。もしかすると他のバリデーションとは別の実装をするほうが良いかもしれません。
UI面では、未入力チェックは他のバリデーションとは少し毛色が違います。初期状態でエラーを示せないので、常時ラベルに「*」や「(必須)」のように明示したり、helperText で補足する必要があると感じました。
Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります
フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。
「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」
フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。
オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。
Next.js, React, TypeScript の相談をする!