【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}
    />
  );
}

textundefined を許可し、初期値を undefined とします。これを「未入力」とし、"" 空文と区別します。これにそって、以下のように空文の判定を書きます。

text !== undefined && !text

空文のときはエラーメッセージを出しますが未入力では出しません。

これにより、表示直後の「未入力」ではエラーメッセージは出ず、入力後に削除したケースでエラーメッセージが表示されるようになります。

「未入力」まわりの TIPS

以下、運用上よくある「未入力」のステータスの取り回しです。

入力の終わりを判定して、未入力メッセージを出す

onBlur={(e) => { setText(e.target.value) }}

入力終わりの判定は onBlur が良さそうです。onChange のハンドルと同じように書けば inputvaluetext ステータスを上書きするので、入力があればそのままに、 undefined であれば 空文に書き換えられ未入力エラーメッセージが表示されます。

submit 時のバリデーションの書き方

  const onSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!text) {
      setText("");
      return;
    }
    hundleSubmit(text);
  };

onSubmit などのタイミングで、実際のエラーメッセージとは別にバリデーションを行う必要があります。この場合は未入力かどうか関係なく、従来どおり text の評価が false ならエラーにします。

また、エラーがあれば、textundefined から空文に変えることでエラーメッセージを更新できます。

リセット(入力破棄)の書き方

  const reset = () => { setText(undefined) };

入力内容を破棄する場合は、空文ではなく未入力をセットすればOKです。

おわりに

実装面では、エラーやエラーメッセージは、適切なタイミングでユーザーに提示する必要があるため、React のステータスの更新と相性が悪いケースが多々あります。コードの見通しとバランスを取れる形を模索する必要があります。もしかすると他のバリデーションとは別の実装をするほうが良いかもしれません。

UI面では、未入力チェックは他のバリデーションとは少し毛色が違います。初期状態でエラーを示せないので、常時ラベルに「*」や「(必須)」のように明示したり、helperText で補足する必要があると感じました。

Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります

フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。

「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」

フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。

オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。

Next.js, React, TypeScript の相談をする!

投稿者 Chaki Hironori

webライターもやってるフロントエンドエンジニアです。Reactは自信があります。またデザイン畑の出身で、気持ちのいいアニメーションやインタラクティブな表現は丁寧に手掛けます。好きなものは中南米の遺跡で、スペイン語が少しできます。