PerplexityでAI駆動開発:入力フォームに文字数制限を追加し、現在の文字数も見える化する

こんにちは。

ベビーが一ヶ月検診を終えたので、少しずつおでかけを始めています。
今日は隣駅のオーケーストアまで買い出しに出かけました。
とはいっても、人混みにはまだ入らないほうがいいと思うので、私の買い出し中は妻とベビーは別行動です。

私達は何度も行ってる場所ですが、ベビーにとってはどれも初めて。
いろんなものを見て聞いて、すくすくと育っていってほしいです。

さて、WEBで入力フォームをつくるとき、大抵は文字数制限がありますよね。
業務で管理画面を作るときも、まず間違いなく文字数制限を設ける必要があります。

今回は、入力フォームの文字数制限のチェック処理と現在の入力文字数の見える化を Perplexity にお願いして作ってみようと思います。

前回の記事までで作成したプロジェクトをベースにしています。

目次

文字数制限を追加して、超えた場合はエラーメッセージを表示する

Perplexityに以下の指示を出しました。

import React, { useState } from ‘react’;
import { TextField, Button, Box, Typography } from ‘@mui/material’;
import “./App.css”
import reactLogo from ‘./assets/react.svg’
import useApi from ‘./hooks/useApi’;

function App() {
const [name, setName] = useState(”);
const [mail, setMail] = useState(”);

const { post } = useApi();

const handleClick = () => {
post(name, mail);
};

return (
<Box sx={{ display: ‘flex’, flexDirection: ‘column’, alignItems: ‘center’, gap: 2, maxWidth: 300, margin: ‘auto’ }}>
<Box sx={{ display: ‘flex’, alignItems: ‘center’, gap: 2, marginBottom: 2 }}>
<img src={reactLogo} alt=”React Logo” style={{ width: 50, height: 50 }} />
<Typography variant=”h5″ component=”h1″>Sample System</Typography>
</Box>

<TextField
label=”Name”
variant=”outlined”
fullWidth
value={name}
onChange={(e) => setName(e.target.value)}
/>

<TextField
label=”Mail Address”
variant=”outlined”
type=”email”
fullWidth
value={mail}
onChange={(e) => setMail(e.target.value)}
/>

<Button
variant=”contained”
color=”primary”
sx={{ marginTop: 2 }}
onClick={handleClick}
>
送信
</Button>
</Box>
);
}

export default App;

上記の TextField の入力フォームに文字数制限を追加したいです。
name に 50字、mail に 255字の文字数制限を追加してください。
なお、最大文字数以上の入力は可能とし、最大文字数を超えた場合に入力欄の下に赤字でエラーメッセージを表示するようにしてください。

前回の記事でも書きましたが、AIに指示を出すときは前提を伝えることが大事です。
前提としてApp.tsx のコードを入力して、それに対して変更してもらうよう指示を出しています。

文字数制限の追加に関する指示は上記のとおりですが、maxlength を指定して最大文字数以上の入力をできないようにするのではなく、最大文字数以上の入力はできるが赤字でエラーメッセージを表示する、という形にしています。

この指示で、以下の回答が得られました。

以下のように、TextField コンポーネントに maxLength 属性を追加し、エラーメッセージを表示するロジックを実装しました。

import React, { useState } from 'react';
import { TextField, Button, Box, Typography } from '@mui/material';
import "./App.css"
import reactLogo from './assets/react.svg'
import useApi from './hooks/useApi';

function App() {
  const [name, setName] = useState('');
  const [mail, setMail] = useState('');
  const [nameError, setNameError] = useState('');
  const [mailError, setMailError] = useState('');

  const { post } = useApi();

  const handleNameChange = (e) => {
    setName(e.target.value);
    if (e.target.value.length > 50) {
      setNameError('名前は50文字以内で入力してください。');
    } else {
      setNameError('');
    }
  };

  const handleMailChange = (e) => {
    setMail(e.target.value);
    if (e.target.value.length > 255) {
      setMailError('メールアドレスは255文字以内で入力してください。');
    } else {
      setMailError('');
    }
  };

  const handleClick = () => {
    post(name, mail);
  };

  return (
    <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, maxWidth: 300, margin: 'auto' }}>
      <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, marginBottom: 2 }}>
        <img src={reactLogo} alt="React Logo" style={{ width: 50, height: 50 }} />
        <Typography variant="h5" component="h1">Sample System</Typography>
      </Box>

      <TextField
        label="Name"
        variant="outlined"
        fullWidth
        value={name}
        onChange={handleNameChange}
        error={!!nameError}
        helperText={nameError}
        inputProps={{ maxLength: 50 }}
      />

      <TextField
        label="Mail Address"
        variant="outlined"
        type="email"
        fullWidth
        value={mail}
        onChange={handleMailChange}
        error={!!mailError}
        helperText={mailError}
        inputProps={{ maxLength: 255 }}
      />

      <Button
        variant="contained"
        color="primary"
        sx={{ marginTop: 2 }}
        onClick={handleClick}
      >
        送信
      </Button>
    </Box>
  );
}

export default App;

主な変更点は以下の通りです:

  1. nameErrormailError の状態を追加し、エラーメッセージを管理します。
  2. handleNameChangehandleMailChange 関数を作成し、入力値の長さをチェックしてエラーメッセージを設定します。
  3. TextField コンポーネントに以下のプロパティを追加しました:
  • onChange: 新しく作成したハンドラー関数を使用
  • error: エラーメッセージが存在する場合に true
  • helperText: エラーメッセージを表示
  • inputProps: maxLength 属性を設定して、入力可能な最大文字数を指定

これにより、名前は50文字、メールアドレスは255文字まで入力可能となり、それぞれの制限を超えた場合にエラーメッセージが表示されます。

概ね問題ないのですが、maxlength が指定されてしまっていたので、削除するよう指示しました。

inputPropsで maxLength を指定している箇所がありますが、これは削除してください

該当箇所が削除されるだけなので、回答は割愛します。

ここまでの変更で、以下の画面が表示されるようになりました。

エラーメッセージはやめて、インジケータに変更する

ここでちょっと気が変わりました。

エラーメッセージを出すのは変わり映えがせずおもしろくないので、インジケータを表示して現在の入力状況がわかるように改修したいと思います。

エラーメッセージの表示はやめて、現在の入力文字数を表示する形に変更したいです。 入力欄の下にインジケータを表示して、現在の入力文字数が視覚的にわかるようにしてください。なお、インジケータが100%(最大文字数以内)は青色、100%を超えた(最大文字数を超えた)場合は赤色に変更してください。 インジケータはMUIのコンポーネントを使用してください。

承知しました。エラーメッセージの代わりに、現在の入力文字数を表示し、視覚的なインジケータを追加します。MUIのLinearProgressコンポーネントを使用してインジケータを実装します。以下が修正後のコードです:

import React, { useState } from 'react';
import { TextField, Button, Box, Typography, LinearProgress } from '@mui/material';
import "./App.css"
import reactLogo from './assets/react.svg'
import useApi from './hooks/useApi';

function App() {
  const [name, setName] = useState('');
  const [mail, setMail] = useState('');

  const { post } = useApi();

  const handleNameChange = (e) => {
    setName(e.target.value);
  };

  const handleMailChange = (e) => {
    setMail(e.target.value);
  };

  const handleClick = () => {
    post(name, mail);
  };

  const getProgressColor = (value, max) => {
    return value > max ? 'error' : 'primary';
  };

  const renderProgress = (value, max) => {
    const progress = (value / max) * 100;
    return (
      <Box sx={{ width: '100%', mt: 1 }}>
        <LinearProgress
          variant="determinate"
          value={Math.min(progress, 100)}
          color={getProgressColor(value, max)}
        />
        <Typography variant="caption" color={getProgressColor(value, max)}>
          {value}/{max}
        </Typography>
      </Box>
    );
  };

  return (
    <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, maxWidth: 300, margin: 'auto' }}>
      <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, marginBottom: 2 }}>
        <img src={reactLogo} alt="React Logo" style={{ width: 50, height: 50 }} />
        <Typography variant="h5" component="h1">Sample System</Typography>
      </Box>

      <TextField
        label="Name"
        variant="outlined"
        fullWidth
        value={name}
        onChange={handleNameChange}
        sx={{ mb: 1 }}
      />
      {renderProgress(name.length, 50)}

      <TextField
        label="Mail Address"
        variant="outlined"
        type="email"
        fullWidth
        value={mail}
        onChange={handleMailChange}
        sx={{ mb: 1 }}
      />
      {renderProgress(mail.length, 255)}

      <Button
        variant="contained"
        color="primary"
        onClick={handleClick}
      >
        送信
      </Button>
    </Box>
  );
}

export default App;

この修正により、以下の変更が加えられました:

  1. エラーメッセージの代わりに、現在の入力文字数と最大文字数を表示します。
  2. MUIのLinearProgressコンポーネントを使用して、視覚的なインジケータを追加しました。
  3. インジケータの色は、最大文字数以内の場合は青色(primary)、超えた場合は赤色(error)に変更されます。
  4. getProgressColor関数を追加して、現在の文字数に基づいて適切な色を返します。
  5. renderProgress関数を追加して、進捗バーと文字数表示を含むコンポーネントを返します。

これにより、ユーザーは視覚的に現在の入力文字数を確認でき、最大文字数を超えた場合はインジケータの色が変わることで注意を促します[1][3][4]。

Citations:
[1] https://stackoverflow.com/questions/58175155/how-to-display-dynamically-the-count-of-number-of-characters-below-the-textfield
[2] https://github.com/mui/material-ui/issues/24681
[3] https://github.com/mui/material-ui/issues/12913
[4] https://www.geeksforgeeks.org/react-mui-textfield-api/
[5] https://stackoverflow.com/questions/45939909/put-length-constraint-in-a-textfield-in-react-js

この変更で、以下の画面が表示されるようになりました。

入力エラーが起きている間は、送信ボタンを押せないようにする

最後に、入力エラーが起きている間は、送信ボタンが非活性になるよう改修します。

入力エラーが起きている場合(入力文字数が最大文字数を超えている場合)は、送信ボタンを disabeld にして押せないようにしてください

入力エラーが発生している場合に送信ボタンを無効化するには、以下のようにAppコンポーネントを修正します:

import React, { useState, useMemo } from 'react';
import { TextField, Button, Box, Typography, LinearProgress } from '@mui/material';
import "./App.css"
import reactLogo from './assets/react.svg'
import useApi from './hooks/useApi';

function App() {
  const [name, setName] = useState('');
  const [mail, setMail] = useState('');

  const { post } = useApi();

  const handleNameChange = (e) => {
    setName(e.target.value);
  };

  const handleMailChange = (e) => {
    setMail(e.target.value);
  };

  const handleClick = () => {
    post(name, mail);
  };

  const getProgressColor = (value, max) => {
    return value > max ? 'error' : 'primary';
  };

  const renderProgress = (value, max) => {
    const progress = (value / max) * 100;
    return (
      <Box sx={{ width: '100%', mt: 1 }}>
        <LinearProgress
          variant="determinate"
          value={Math.min(progress, 100)}
          color={getProgressColor(value, max)}
        />
        <Typography variant="caption" color={getProgressColor(value, max)}>
          {value}/{max}
        </Typography>
      </Box>
    );
  };

  const isFormValid = useMemo(() => {
    return name.length <= 50 && mail.length <= 255;
  }, [name, mail]);

  return (
    <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, maxWidth: 300, margin: 'auto' }}>
      <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, marginBottom: 2 }}>
        <img src={reactLogo} alt="React Logo" style={{ width: 50, height: 50 }} />
        <Typography variant="h5" component="h1">Sample System</Typography>
      </Box>

      <TextField
        label="Name"
        variant="outlined"
        fullWidth
        value={name}
        onChange={handleNameChange}
      />
      {renderProgress(name.length, 50)}

      <TextField
        label="Mail Address"
        variant="outlined"
        type="email"
        fullWidth
        value={mail}
        onChange={handleMailChange}
      />
      {renderProgress(mail.length, 255)}

      <Button
        variant="contained"
        color="primary"
        onClick={handleClick}
        disabled={!isFormValid}
      >
        送信
      </Button>
    </Box>
  );
}

export default App;

この修正では、以下の変更を行いました:

  1. isFormValidという新しい変数をuseMemoフックを使用して作成しました。この変数は、nameとmailの長さが最大文字数以内であるかどうかをチェックします[1][5]。
  2. Buttonコンポーネントにdisabled={!isFormValid}プロパティを追加しました。これにより、フォームが無効な場合(つまり、いずれかの入力が最大文字数を超えている場合)、ボタンが無効化されます[1][3]。

この実装により、nameが50文字を超えるか、mailが255文字を超えた場合、送信ボタンが自動的に無効化されます。ユーザーは入力エラーを修正するまで送信ボタンを押すことができなくなります[4]。

Citations:
[1] https://www.dhiwise.com/post/the-ultimate-guide-to-react-button-disabled-best-practices
[2] https://www.youtube.com/watch?v=KLXDe-w5SX4
[3] https://www.geeksforgeeks.org/how-to-disable-a-button-in-reactjs/
[4] https://stackoverflow.com/questions/35978070/how-to-disable-form-submit-button-until-all-input-fields-are-filled-reactjs-es
[5] https://zenn.dev/purratto/articles/156b833a4fc5d7
[6] https://goshacmd.com/form-recipe-disable-submit-button-react/

最大文字数を超えた場合、送信ボタンが非活性になることが確認できました。

急な仕様変更も怖くない。アプリ開発を加速させよう

いかがだったでしょうか?

このように、途中で気が変わっても気軽に変更できるのが、AIを使うことの大きなメリットですね!

この変更を自分でやろうとすると、以下のようなことを考える必要があると思います。

  • どのようなUIにするかの検討
  • MUIのどのコンポーネントを使えばよいか調べる
  • 使えそうなコンポーネントが見つかったら、その使い方を調べる
  • コーディング

これらを手作業でやると、1〜2時間はかかってしまいそうですね。
ですが、AIにお願いすればものの数分で出来上がってしまいます。

この旨味を知ってしまうと、もう自分でコードを書くのが嫌になってしまいます(^_^;)

でも、私はそれを悪いことだとは思いません。
AIにお願いすることで省けるものはどんどん省いて、アプリ開発を加速させていきましょう。

ぜひPerplexityでAI駆動開発にトライしてみてくださいね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

28歳のときに音楽家からエンジニアに転向。
WEB系のシステム開発(Java, C#, Vue, React など)に携わっています。

「できる」を増やしてワクワクな人生を送るための情報をシェアしています。

コメント

コメントする

目次