はじめに
入力値のバリデーションが行われるタイミングは様々です。
システムによっても違うし、人によっても違うでしょう。
この入力欄シリーズでは、以下のタイミングが登場しました。
- SUBMITボタンが押されたタイミング(
onSubmit
イベント) - 入力欄からフォーカスが外れたタイミング(
onBlur
イベント) - 入力値が変化したタイミング(
onChange
イベント)
今回は、Reactならではのタイミングでバリデーションする方法として、useEffect
を使った例をご紹介します。
実物はこちら
リポジトリはこちら
概要
useEffect
はReact組み込みのフックで、副作用を扱います。
副作用は、平たく言うと「ある値が変わると他に影響が出る」といった感じでしょうか。
useEffect
を使うことで「ある値が変わると◯◯」の「◯◯」の部分を処理することが可能になります。
この記事を読むことで、以下のような画面を作れるようになります。
以下の仕様を想定して作っています。
- 「名前」「かな」の二つの入力欄がある
- 両方とも必須で、30字以内という制限がある
- バリデーションは、画面を表示したとき、入力値が変化したときに行われる
- エラーメッセージは、それぞれの入力欄の下に赤字で表示する
- エラーのある入力欄は赤く表示する
実装
先にコード全容です。
'use client'
import {Box, Button, Typography} from "@mui/material";
import {ChangeEvent, FormEvent, useEffect, useState} from "react";
import './style.css'
import {ValidateError} from "@/types/validate-scratch";
import {ValidateUseEffectForm} from "@/types/validate-use-effect";
export default function ValidateUseEffect() {
const [formData, setFormData] = useState<ValidateUseEffectForm>({
fullName: '',
fullNameKana: '',
})
const [errors, setErrors] = useState<ValidateError[]>([])
useEffect(() => {
const validate = (propertyName: string, value: string): ValidateError[] => {
const errors: ValidateError[] = [];
if (value === '') {
errors.push({
key: propertyName,
message: '入力してください'
})
}
if ((value as string).length > 30) {
errors.push({
key: propertyName,
message: '30字以下で入力してください'
})
}
return errors
}
setErrors([
...validate('fullName', formData.fullName),
...validate('fullNameKana', formData.fullNameKana)
])
}, [formData]);
const onSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
setErrors(errors)
if (errors.length) {
return
}
alert('No validate errors!')
}
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
switch (event.target.name) {
case 'fullName': {
setFormData({...formData, fullName: event.target.value})
break
}
case 'fullNameKana': {
setFormData({...formData, fullNameKana: event.target.value})
break
}
default:
}
}
const getErrorByPropertyName = (propertyName: string) => {
return errors.filter(error => error.key === propertyName)
}
const getErrorMessageByPropertyName = (propertyName: string) => {
return getErrorByPropertyName(propertyName).map((error, index) => <span key={`${index}-${error.key}`}>{error.message}</span>)
}
const isError = (propertyName: string) => {
return getErrorByPropertyName(propertyName).length > 0
}
return (
<main>
<Box mt={2} ml={2}>
<form onSubmit={onSubmit}>
<Box mb={2}>
<Box mb={1}>
<span>名前</span>
</Box>
<Box>
<input type="text" name="fullName" className={isError('fullName') ? 'error' : ''} onChange={handleChange} />
</Box>
<Box height="1rem" sx={{color: 'red'}}>
{
getErrorMessageByPropertyName('fullName')
}
</Box>
</Box>
<Box mb={2}>
<Box mb={1}>
<span>かな</span>
</Box>
<Box>
<input type="text" name="fullNameKana" className={isError('fullNameKana') ? 'error' : ''} onChange={handleChange} />
</Box>
<Box height="1rem" sx={{color: 'red'}}>
{
getErrorMessageByPropertyName('fullNameKana')
}
</Box>
</Box>
<Box>
<Button type="submit" variant="contained" disabled={errors.length > 0}>SUBMIT</Button>
</Box>
</form>
</Box>
<Box mt={2} ml={2}>
<Box>
<Typography variant="body2">
{formData.fullNameKana !== '' && `${formData.fullNameKana}さん、こんにちは。` }
</Typography>
</Box>
<Box>
<Typography variant="h4">
{formData.fullName !== '' && `${formData.fullName}さん、こんにちは。` }
</Typography>
</Box>
</Box>
</main>
)
}
各部分の解説
useEffectで入力値の変化を監視する
useEffect
を使うことで「ある値が変わると◯◯」の「◯◯」の部分を処理することが可能になる、と書きました。
今回の場合でいうと、「入力値が変わったらバリデーションする」ということができます。
useEffect(() => {
const validate = (propertyName: string, value: string): ValidateError[] => {
const errors: ValidateError[] = [];
if (value === '') {
errors.push({
key: propertyName,
message: '入力してください'
})
}
if ((value as string).length > 30) {
errors.push({
key: propertyName,
message: '30字以下で入力してください'
})
}
return errors
}
setErrors([
...validate('fullName', formData.fullName),
...validate('fullNameKana', formData.fullNameKana)
])
}, [formData]);
ハイライトしている useEffect
の第二引数に formData
を配列で渡しています。
この第二引数の配列をよく依存配列と呼びます。
その名のとおりuseEffect
が依存している値となり、この useEffect
内の処理は依存する formData
が変化すると実行されます。
useEffect
内で宣言している処理は、これまで onBlur
や onChange
で使ってきたバリデーション処理です。
注意点としては、useEffect
内の処理は「画面表示時にも実行される」ということです。
今回の場合でいうと、この画面を表示するとまず最初にバリデーションが実行されます。
画面を表示してまだ何も操作していないのに赤字で「入力してください」と注意されるのは嫌だ、という人がいるかもしれませんね。
そのような場合は、別途対策が必要になります。
おわりに
いかがだったでしょうか?
今回は入力フォームの値を useEffect
を使ってバリデーションしてみました。
この useEffect
はなかなかの曲者で、ちょっと間違えると処理が無限ループしてしまうのはReactあるあるですね。
とはいえ、使わないと慣れるものも慣れません。
失敗してもいいので、まずはどんどん使ってみましょう!
ハコラトリは、Reactを習得したい駆け出しエンジニアを応援しています。
Reactレシピでは、これからもReactで色々なUIを作って紹介していきますので、「こんなUIを作ってみてほしい」「こんな場合はどうすれば?」といったアイディアを募集中です!
コメントお待ちしております。
コメント