こんにちは。
強力粉を早く使い切りたいはこだてたろうです。
小麦粉はお肉をカリッと炒めるときなど、何かと必要になります。
しかし、我が家には強力粉しかありません。
強力粉でもいけないことはないのですがモチモチしすぎちゃうので、薄力粉のほうが適しています。
早く使い切って、早く薄力粉でカリッと仕上げたいです。
はじめに
さて、久しぶりのReactレシピシリーズです。
今回から複数回にわけて、バリデーションつき入力欄のコンポーネントをいい感じに作っていきたいと思います。
「いい感じ」とはどんな感じかというと、単一責任の原則を意識したり、コンポジションを頑張ってみたり、レイアウトはラッパーに任せるようにしたり、という感じです。
入門書とかを読んでいると、コンポーネントの基本的な作り方は載っていますが、こういう「いい感じ」なコンポーネントの作り方についてはあまり触れられません(入門書なのでそもそも触れなくてもいい)。
私自身、「いい感じ」のコンポーネントがちゃんと作れるかわからなかったので、この記事でトライしてみたいと思います。
実物はこちら
リポジトリはこちら
この記事を読むことで作れるもの
この記事を読むことで、以下のような画面が作れるようになります。

実装
先にコード全容です。
今回は準備編として、コンポーネント化していないフラットな状態のコードです。
'use client'
import React, { useState, useId, ChangeEvent, FormEvent } from "react";
import './style.css'
import { Box } from "@mui/material";
function InputForm() {
const [name, setName] = useState<string>('');
const [nameKana, setNameKana] = useState<string>('');
const [nameError, setNameError] = useState<string>('');
const [nameKanaError, setNameKanaError] = useState<string>('');
const nameId = useId();
const nameKanaId = useId();
const handleNameChange = (event: ChangeEvent<HTMLInputElement>): void => {
const newValue = event.target.value;
setName(newValue);
validateName(newValue);
};
const handleNameKanaChange = (event: ChangeEvent<HTMLInputElement>): void => {
const newValue = event.target.value;
setNameKana(newValue);
validateNameKana(newValue);
};
const validateName = (value: string): void => {
if (value.trim() === '') {
setNameError('必須項目です。');
} else if (value.length > 50) {
setNameError('50文字以内で入力してください。');
} else if (value.length < 2) {
setNameError('2文字以上で入力してください。');
} else if (!/^[A-Za-z\s]+$/.test(value)) {
setNameError('英字のみ使用できます。');
} else {
setNameError('');
}
};
const validateNameKana = (value: string): void => {
if (value.trim() === '') {
setNameKanaError('必須項目です。');
} else if (value.length > 50) {
setNameKanaError('50文字以内で入力してください。');
} else if (value.length < 2) {
setNameKanaError('2文字以上で入力してください。');
} else if (!/^[\u30A0-\u30FF\u3000]+$/.test(value)) {
setNameKanaError('カタカナのみ使用できます。');
} else {
setNameKanaError('');
}
};
const handleSubmit = (event: FormEvent<HTMLFormElement>): void => {
event.preventDefault();
console.log({ name, nameKana });
};
return (
<main>
<Box mt={2} ml={2}>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor={nameId}>名前</label>
<div>
<input
id={nameId}
type="text"
value={name}
onChange={handleNameChange}
placeholder="名前を入力してください"
/>
</div>
<div style={{color: 'red', fontSize: '1rem', minHeight: '1.5rem', marginTop: '0.5rem'}}>
<span>{nameError}</span>
</div>
</div>
<div style={{marginTop: '1rem'}}>
<label htmlFor={nameKanaId}>名前(カナ)</label>
<div>
<input
id={nameKanaId}
type="text"
value={nameKana}
onChange={handleNameKanaChange}
placeholder="名前(カナ)を入力してください"
/>
</div>
<div style={{color: 'red', fontSize: '1rem', minHeight: '1.5rem', marginTop: '0.5rem'}}>
<span>{nameKanaError}</span>
</div>
</div>
<div style={{marginTop: '1rem'}}>
<button type="submit">SUBMIT</button>
</div>
</form>
</Box>
</main>
);
}
export default InputForm;
各部分の解説
コンポーネント化の対象となる入力欄
このページには、「名前」と「名前(カナ)」の2つの入力欄があります。
それぞれラベルと入力欄、バリデーション機能、そしてバリデーションエラー時に表示されるエラーメッセージで構成されています。
// バリデーション機能。必須チェックや文字数チェック、文字種チェックをしている。
const validateName = (value: string): void => {
if (value.trim() === '') {
setNameError('必須項目です。');
} else if (value.length > 50) {
setNameError('50文字以内で入力してください。');
} else if (value.length < 2) {
setNameError('2文字以上で入力してください。');
} else if (!/^[A-Za-z\s]+$/.test(value)) {
setNameError('英字のみ使用できます。');
} else {
setNameError('');
}
};
<div>
<label htmlFor={nameId}>名前</label>
<div>
<input
id={nameId}
type="text"
value={name}
onChange={handleNameChange}
placeholder="名前を入力してください"
/>
</div>
<div style={{color: 'red', fontSize: '1rem', minHeight: '1.5rem', marginTop: '0.5rem'}}>
<span>{nameError}</span>
</div>
</div>
このあたりのコードを、いい感じにコンポーネント化していきたいと思います。
イメージ的には、以下のような形になるだろうと考えています。
<div>
<Label for={nameId}>名前</Label>
<div>
<InputField
id={nameId}
type="text"
value={name}
onChange={handleNameChange}
placeholder="名前を入力してください"
/>
</div>
<div style={{minHeight: '1.5rem', marginTop: '0.5rem'}}>
<ErrorMessage>{nameError}</ErrorMessage>
</div>
</div>
ラベルと入力欄、エラーメッセージをそれぞれ小さいコンポーネントとして抽出します。
レイアウトは、これらのコンポーネントの呼び出し側で制御します。
最終的には、バリデーション機能つきの入力欄コンポーネントとして TextInputWithValidation
コンポーネントができる予定です。
おわりに
いかがだったでしょうか?
今回は準備編として、コンポーネント化する前のフラットな状態のコードを提示し、コンポーネント化するイメージを少しお伝えしました。
次回の記事では、早速コンポーネント化にとりかかっていきたいと思います。
ご自身でトライしてみたい方は、ぜひ実装してみてくださいね。
ハコラトリでは、「できる」を増やしてワクワクする人生を送るための情報をシェアしています。
ぜひ一緒にワクワクな人生を送れるように「できる」を増やしていきましょう。
これからもReactで色々なUIを作って紹介していきますので、「こんなUIを作ってみてほしい」「こんな場合はどうすれば?」といったアイディアを募集中です!
コメントお待ちしております。
コメント