はじめに
入力値のバリデーションが行われるタイミングは様々です。
システムによっても違うし、人によっても違うでしょう。
この入力欄シリーズでは、以下のタイミングが登場しました。
- 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を作ってみてほしい」「こんな場合はどうすれば?」といったアイディアを募集中です!
コメントお待ちしております。
 
			 
			 
			 
			 
			 
			
コメント