Cloudscapeでフォームバリデーションを実装してみる

以前 Vue.js + Vuetify で実装したフォームをCloudscape (+Next.js) を使って実装し、バリデーションを動作させてみました。
2023.07.07

最近は、Cloudscape を使った開発業務も行っています。

Cloudscape について

Cloudscape is an open source design system to create web applications. It was built for and is used by Amazon Web Services (AWS) products and services.

しばたさんが、今回と同様の構成(Cloudscape + Next.js)の記事を書かれていますので、そちらもご覧ください。

AWSマネージメントコンソールで採用されているので、コンソールで見慣れた(ものに近い)各種コンポーネントが利用出来ます。

割と頻繁に更新されていて、いつの間にか新しいコンポーネントや Props が生えてきたりしています。

以前、Vue + Vuetify でフォームバリデーションを実装する記事を書いたので、今回は Cloudscape(+Next.js) で実装してみました。

バリデーションの指針については、Cloudscape で定義されているものに従っていきます。

Vuetify だと入力毎や、フォーカスが外れたタイミングで検証するのが標準的な感じでしたが、Cloudscape だと Submit まで検証しない、と明確にかかれています。 この辺りは思想の違いでしょうか。

1. The user begins typing data into a form for the first time. Don't validate the data yet.
2. The user submits the form. Validate the data.
3. Mark any fields that contain an error, display an error message under each field, and configure the page to scroll to the topmost error. (Don’t display a generic message at the top of the page, such as "Fix all errors on this page.")
(後略)

バリデーションに限らずいろんな方針について明記されているため、迷わず実装を進められるのは魅力です。 逆に言うと、方針に外れるようなことはやりにくいため、柔軟性に欠けるという一面もあります。 そのため、要件によって向き不向きが分かれそうなデザインシステム、という印象を持っています。

フォームとバリデーションを実装

以前と同様に、AWSアカウントIDとバケット名を入力して登録するフォームを Cloudscape で実装してみました。

環境情報は以下の通りです。

  • Cloudscape v3.0.331 (ブログ書いている時点で最新は v3.0.333。前日にアプリ構築したばかりなのに…)
  • Next.js v13.4.8
  • React v18.2.0
  • Node.js v18.16.1

フォームを開いた直後の画面です。

フォームを開いた直後の画面

バリデーション違反の状態で Submit ボタンを押した時の画面です。 エラーメッセージとエラー箇所の強調などいい感じで描画してくれています。

バリデーションエラー画面

フォームコンポーネントの実装は以下の通りです。

TestForm.tsx

TestForm.tsx

import * as React from "react";
import {Input, SpaceBetween, FormField, Header, Container, Form, Button} from "@cloudscape-design/components";

const TestForm = () => {
  const [awsAccountId, setAwsAccountId] = React.useState("");
  const [bucketName, setBucketName] = React.useState("");
  const [errorMessages, setErrorMessages] = React.useState({awsAccountId: "", bucketName: ""});

  return (
    <form
      onSubmit={e => {
        e.preventDefault();
        const errorMessage = {awsAccountId: "", bucketName: ""};

        /^[0-9]{12}$/.test(awsAccountId) || (errorMessage["awsAccountId"] = "AWSアカウントID は12桁の数値です。");
        awsAccountId || (errorMessage["awsAccountId"] = "必須項目です。");
        /^[a-z0-9.-]{3,63}$/.test(bucketName) || (errorMessage["bucketName"] = "バケット名 のフォーマットが正しくありません。");
        bucketName || (errorMessage["bucketName"] = "必須項目です。");
        setErrorMessages(errorMessage);

        if (!errorMessage["awsAccountId"] && !errorMessage["bucketName"]) {
          console.log('success');
        }
      }}>
      <Form
        actions={
          <SpaceBetween direction="horizontal" size="xs">
            <Button variant="primary">Submit</Button>
          </SpaceBetween>
        }
      >
        <Container
          header={
            <Header
              variant="h2"
            >
              Test Form
            </Header>
          }
        >
          <SpaceBetween size="l">
            <FormField
              constraintText="12桁の数字を入力してください。"
              description="設定したいAWSアカウントIDを入力してください。"
              errorText={errorMessages["awsAccountId"]}
              label="AWSアカウントID"
            >
              <Input
                placeholder="123456789012"
                onChange={({ detail }) => setAwsAccountId(detail.value)}
                value={awsAccountId}
                ariaRequired={true}
              />
            </FormField>
            <FormField
              constraintText="3文字以上63文字以下の数字、小文字、ピリオド、ハイフンを入力してください。"
              description="設定したいバケット名を入力してください。"
              errorText={errorMessages["bucketName"]}
              label="バケット名"
            >
              <Input
                placeholder="mybucket"
                onChange={({ detail }) => setBucketName(detail.value)}
                value={bucketName}
                ariaRequired={true}
              />
            </FormField>
          </SpaceBetween>
        </Container>
      </Form>
    </form>
  );
}

export default TestForm;

考察

Cloudscape 自体にバリデーションを支援するような仕組みは特に入っておらず、 <FormField>errorText に何か文字列が入っていればエラー文言として表示する、というシンプルなものです。

そのため、エラー状態やエラーメッセージの管理はすべて自前で行わなければならず、いかにも辛そうな感じです。

そこで、実際の開発では React Hook Form と組み合わせて使っています。

React Hook Form は大変機能が豊富なため、今回詳細は割愛します。 記事もあるので、そちらをご覧ください。

Cloudscape と React Hook Form を繋いだ実装については、別の機会に書いてみたいと思います。