Reactで作った自前バリデーションつき入力フォームの入力値をstateでインタラクティブに扱う

目次

はじめに

入力欄に入力された値を使ってあれやこれやしたいこと、ありますよね?

今回は、前回作成した入力フォームを改修して、入力値を state でインタラクティブに扱うようにしてみました。

入力値を state で扱うことは React をやっていれば避けては通れないので、必ず習得しておきましょう!

実物はこちら

あわせて読みたい
React Recipe Reactアプリのレシピ集

リポジトリはこちら

GitHub
GitHub - hakoratory/react-recipe Contribute to hakoratory/react-recipe development by creating an account on GitHub.

概要

この記事を読むことで、以下のような画面を作れるようになります。

以下の仕様を想定して作っています。

  • 「名前」「かな」の二つの入力欄がある
  • 両方とも必須で、30字以内という制限がある
  • バリデーションは、入力欄に値が入力されたとき、SUBMITボタンが押されたときに行われる
  • エラーメッセージは、それぞれの入力欄の下に赤字で表示する
  • エラーのある入力欄は赤く表示する

実装

先にコード全容です。

'use client'

import {Box, Button, Typography} from "@mui/material";
import {ChangeEvent, FormEvent, useState} from "react";
import './style.css'
import {ValidateError} from "@/types/validate-scratch";


export default function ValidateState() {
    const [fullName, setFullName] = useState<string>('')
    const [fullNameKana, setFullNameKana] = useState<string>('')
    const [errors, setErrors] = useState<ValidateError[]>([])

    const onSubmit = (event: FormEvent<HTMLFormElement>) => {
        event.preventDefault()

        const errors = validateForm()
        setErrors(errors)
        if (errors.length) {
            return
        }

        alert('No validate errors!')
    }

    const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
        switch (event.target.name) {
            case 'fullName': {
                setFullName(event.target.value)
                break
            }
            case 'fullNameKana': {
                setFullNameKana(event.target.value)
                break
            }
            default:
        }

        const errorsWithoutTarget = errors.filter(error => error.key !== event.target.name)
        const newErrors = errorsWithoutTarget.concat(validate(event.target.name, event.target.value))
        setErrors(newErrors)
    }

    const validateForm = (): ValidateError[] => {
        return [
            ...validate('fullName', fullName),
            ...validate('fullNameKana', fullNameKana)
        ]
    }

    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
    }

    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">
                        {fullNameKana !== '' && `${fullNameKana}さん、こんにちは。` }
                    </Typography>
                </Box>
                <Box>
                    <Typography variant="h4">
                        {fullName !== '' && `${fullName}さん、こんにちは。` }
                    </Typography>
                </Box>
            </Box>
        </main>
)
}

各部分の解説

入力値のstateの宣言

「名前」「かな」に対応する state を宣言します。
特に変わったところはなく、一般的な useState の使い方ですね。

    const [fullName, setFullName] = useState<string>('')
    const [fullNameKana, setFullNameKana] = useState<string>('')

入力値の変更を state に反映する

前回は onBlur で処理していた箇所を、onChange に変更して、入力値がすぐに state に反映されるようにしました。
バリデーションも忘れずに行いましょう。

    const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
        switch (event.target.name) {
            case 'fullName': {
                setFullName(event.target.value)
                break
            }
            case 'fullNameKana': {
                setFullNameKana(event.target.value)
                break
            }
            default:
        }

        const errorsWithoutTarget = errors.filter(error => error.key !== event.target.name)
        const newErrors = errorsWithoutTarget.concat(validate(event.target.name, event.target.value))
        setErrors(newErrors)
    }

入力値をインタラクティブに表示する

state を使うことで、入力値の変更を即座に画面に反映することができるようになります。

            <Box mt={2} ml={2}>
                <Box>
                    <Typography variant="body2">
                        {fullNameKana !== '' && `${fullNameKana}さん、こんにちは。` }
                    </Typography>
                </Box>
                <Box>
                    <Typography variant="h4">
                        {fullName !== '' && `${fullName}さん、こんにちは。` }
                    </Typography>
                </Box>
            </Box>
        </main>

今回は、ただ画面に入力値を表示するだけでしたが、加工して表示したり条件判定に使ったり、いろんなことができます。

おわりに

いかがだったでしょうか?
今回は入力フォームの値を state でインタラクティブに扱ってみました。

state は React で開発する際の醍醐味なので、ぜひマスターしてくださいね。

ハコラトリは、Reactを習得したい駆け出しエンジニアを応援しています。

Reactレシピでは、これからもReactで色々なUIを作って紹介していきますので、「こんなUIを作ってみてほしい」「こんな場合はどうすれば?」といったアイディアを募集中です!
コメントお待ちしております。

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

この記事を書いた人

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

自分らしく力を発揮できて、自信を持って生きられる人を増やすための活動をしています。

コメント

コメントする

目次