본문 바로가기
SPA/React

[React] 사용해본 yup 조건절 정리

by J4J 2022. 8. 11.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 제가 사용해본 yup 조건절들을 정리해보는 시간을 가져보려고 합니다.

 

 

 

들어가기에 앞서 yup과 함께 react-hook-form을 이용하여 코드가 작성되어 있다는 점 참고 부탁드립니다.

 

 

반응형

 

 

Base - 1

 

먼저 가장 기본이 되는 조건절입니다.

 

간단한 예시로 다음과 같은 코드를 작성할 수 있습니다.

 

import * as React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';

interface Iform {
    displayYn: string;
    displayContent: string;
}

const Base1 = () => {
    const schema = yup.object().shape({
        displayYn: yup.string().required('displayYn을 선택해주세요'),
        displayContent: yup
            .string()
            .when('displayYn', (displayYn, schema) => 
                displayYn === 'Y' ? schema.required('displayContent를 입력해주세요.') : schema
            )
    })

    const { register, handleSubmit, formState: { errors } } = useForm<Iform>({
        resolver: yupResolver(schema),
    })

    const clickCheck = () => {
        alert('Success');
    }

    return (
        <div>
            <div>
                <select {...register('displayYn')}>
                    <option value="">blank</option>
                    <option value="Y">Yes</option>
                    <option value="N">No</option>
                </select>
                <span>{errors.displayYn && errors.displayYn.message}</span>
            </div>

            <div>
                <input type="text" {...register('displayContent')} />
                <span>{errors.displayContent && errors.displayContent.message}</span>
            </div>

            <button onClick={handleSubmit(clickCheck)}>검증</button>
        </div>
    )
}

export default Base1;

 

 

 

위 코드의 유효성이 검증되는 절차는 다음과 같습니다.

 

  • displayYn을 선택하지 않을 경우 에러 발생
  • displayYn을 Yes로 선택하고 displayContent를 입력하지 않을 경우 에러 발생

 

 

 

 

Base - 2

 

이번에도 기본으로 사용되는 코드입니다.

 

결과는 위와 동일한 결과가 나오지만, when 절을 사용할 때 위와 달리 is, then 구문을 이용한 코드입니다.

 

import * as React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';

interface Iform {
    displayYn: string;
    displayContent: string;
}

const Base2 = () => {
    const schema = yup.object().shape({
        displayYn: yup.string().required('displayYn을 선택해주세요'),
        displayContent: yup
            .string()
            .when('displayYn', {
                is: (displayYn: string) => displayYn === 'Y',
                then: yup.string().required('displayContent를 입력해주세요.')
            })
    })

    const { register, handleSubmit, formState: { errors } } = useForm<Iform>({
        resolver: yupResolver(schema),
    })

    const clickCheck = () => {
        alert('Success');
    }

    return (
        <div>
            <div>
                <select {...register('displayYn')}>
                    <option value="">blank</option>
                    <option value="Y">Yes</option>
                    <option value="N">No</option>
                </select>
                <span>{errors.displayYn && errors.displayYn.message}</span>
            </div>

            <div>
                <input type="text" {...register('displayContent')} />
                <span>{errors.displayContent && errors.displayContent.message}</span>
            </div>

            <button onClick={handleSubmit(clickCheck)}>검증</button>
        </div>
    )
}

export default Base2;

 

 

 

 

검증 데이터 커스텀하여 검증 - 1

 

일반적으로 한 데이터 값을 검증하기 위해 많이 사용되는 구문이 required, nullable, length 등이 있을 겁니다.

 

하지만 유효성 검증을 하다 보면 위와 같이 제공해주는 구문 가지고는 쉽지 않은 케이스들이 있었습니다.

 

이를 해결하고자 when을 다음과 같이 사용해본 경험이 있습니다.

 

displayContent: yup
    .string()
    .when('displayContent', (displayContent, schema) => 
        ~~~
    )

 

 

 

하지만 위와 같이 사용할 경우 에러가 발생됩니다.

 

검증하려는 데이터의 when절에 검증 데이터 자체가 다시 들어갈 수 없기 때문입니다.

 

이런 상황에서 다음과 같은 코드 형태를 통해 해결한 적이 있었습니다.

 

import * as React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';

interface Iform {
    displayContent: string;
}

const Self1 = () => {
    const schema = yup.object().shape({
        displayContent: yup
            .string()
            .test({
                message: 'displayContent를 확인해주세요.',
                test: (displayContent) => {
                    if(displayContent?.includes('test') && displayContent.length > 5) {
                        return true;
                    } else {
                        return false;
                    }
                }
            })
    })

    const { register, handleSubmit, formState: { errors } } = useForm<Iform>({
        resolver: yupResolver(schema),
    })

    const clickCheck = () => {
        alert('Success');
    }

    return (
        <div>
            <div>
                <input type="text" {...register('displayContent')} />
                <span>{errors.displayContent && errors.displayContent.message}</span>
            </div>

            <button onClick={handleSubmit(clickCheck)}>검증</button>
        </div>
    )
}

export default Self1;

 

 

 

when대신 test를 사용한 케이스이고 test를 사용할 경우 검증 데이터 자체를 test 하는 함수에 파라미터로 불러와 사용할 수 있었기 때문에 보다 편리하게 유효성 검증을 위한 코드를 작성할 수 있었습니다.

 

추가로 위 코드의 유효성이 검증되는 절차는 다음과 같습니다.

 

  • displayContent값에 'test'가 포함되어있고 길이가 5이상이 아닐 경우 에러 발생

 

 

 

 

검증 데이터 커스텀하여 검증 - 2

 

이번엔 검증 데이터가 배열일 때 예시입니다.

 

해결하는 방법은 위와 유사합니다.

 

다음과 같이 코드를 작성하게 된다면 배열 내부에 있는 데이터 값을 조건에 포함시킬 때 보다 편리하게 포함시킬 수 있었습니다.

 

import * as React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';

interface Iobj {
    name: string;
    age: number;
}

interface Iform {
    objList: Iobj[];
}

const Self2 = () => {
    const schema = yup.object().shape({
        objList: yup.array().test({
            message: 'objList를 확인해주세요.',
            test: (objList) => {
                if(objList) {
                    const filterObjList = objList?.filter((obj) => obj.name.includes('test'));
                    return filterObjList.length > 0;
                }

                return false;
            }
        })
    })

    const { 
        register, 
        handleSubmit, 
        formState: { errors }, 
        setValue, 
        getValues,
        watch 
    } = useForm<Iform>({
        resolver: yupResolver(schema),
        defaultValues: {
            objList: []
        }
    })

    const clickAdd = () => {
        setValue('objList', [...getValues('objList'), { name: '', age: 0 }])
    }

    const clickCheck = () => {
        alert('Success');
    }

    return (
        <div>
            <div>
                {watch('objList')?.map((obj, index) => (
                    <div key={index}>
                        <input type="text" {...register(`objList.${index}.name`)} />
                        <input type="number" {...register(`objList.${index}.age`)} />
                    </div>
                ))}

                <span>{errors.objList && errors.objList.message}</span>
            </div>

            <button onClick={clickAdd}>추가</button>
            <button onClick={handleSubmit(clickCheck)}>검증</button>
        </div>
    )
}

export default Self2;

 

 

 

위 코드의 유효성이 검증되는 절차는 다음과 같습니다.

 

  • 배열에 있는 각 요소들의 name값을 확인하여 하나라도 'test'가 포함되어 있지 않으면 에러 발생

 

 

 

 

객체 내부에 있는 객체 검증

 

최근 유효성 검사를 할 때 가장 머리를 아프게 했던 상황이었습니다.

 

상황은 말 그대로 객체 내부에 있는 객체를 검증하는 건데, 내부에 있는 객체를 검증할 때 부모 객체에 있는 다른 변수 값을 활용해야 했습니다.

 

이런 상황에서는 다음과 같이 코드를 작성해서 해결할 수 있었습니다.

 

import * as React from 'react';
import { useForm } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';

interface Iobj {
    addObjYn: string;
    name: string;
    age: number;
}

interface Iform {
    objYn: string;
    obj: Iobj;
}

const ObjectInObject = () => {
    const schema = yup.object().shape({
        objYn: yup.string().required('objYn 선택해주세요'),
        obj: yup
            .mixed()
            .when('objYn', {
                is: (objYn: string) => objYn === 'Y',
                then: yup.object().shape({
                    name: yup.string().when('addObjYn', (addObjYn, schema) => 
                        addObjYn === 'Y' ? schema.required('name을 입력해주세요.') : schema
                    ),
                    age: yup.number().min(3, 'age를 3이상 입력해주세요.')
                })
            })
    })

    const { register, handleSubmit, formState: { errors } } = useForm<Iform>({
        resolver: yupResolver(schema),
    })

    const clickCheck = () => {
        alert('Success');
    }

    return (
        <div>
            <div>
                <select {...register('objYn')}>
                    <option value="">blank</option>
                    <option value="Y">Yes</option>
                    <option value="N">No</option>
                </select>
                <span>{errors.objYn && errors.objYn.message}</span>
            </div>

            <div>
                <div>
                    <select {...register('obj.addObjYn')}>
                        <option value="">blank</option>
                        <option value="Y">Yes</option>
                        <option value="N">No</option>
                    </select>
                </div>

                <div>
                    <input type="text" {...register('obj.name')} />
                    <span>{errors.obj?.name && errors.obj.name.message}</span>
                </div>

                <div>
                    <input type="number" {...register('obj.age')} />
                    <span>{errors.obj?.age && errors.obj.age.message}</span>
                </div>
            </div>

            <button onClick={handleSubmit(clickCheck)}>검증</button>
        </div>
    )
}

export default ObjectInObject;

 

 

 

먼저 위 코드의 유효성이 검증되느 절차는 다음과 같습니다.

 

  • objYn을 선택하지 않을 경우 에러 발생
  • objYn을 Yes로 선택하고 addObjYn도 Yes로 선택했는데 name이 입력되지 않은 경우 에러 발생
  • objYn을 Yes로 선택하고 age를 3이상 입력하지 않을 경우 에러 발생

 

 

 

 

그리고 여기서 잘 보고 넘어가셔야 될 점은 obj의 유효성을 체크할 때 mixed를 사용했다는 것입니다.

 

처음에는 obj를 검사하니까 다음과 같이 object()를 이용하여 코드를 작성했었습니다.

 

const schema = yup.object().shape({
    objYn: yup.string().required('objYn 선택해주세요'),
    obj: yup
        .object() // <-- 여기
        .when('objYn', {
            is: (objYn: string) => objYn === 'Y',
            then: yup.object().shape({
                name: yup.string().when('addObjYn', (addObjYn, schema) => 
                    addObjYn === 'Y' ? schema.required('name을 입력해주세요.') : schema
                ),
                age: yup.number().min(3, 'age를 3이상 입력해주세요.')
            })
        })
})

 

 

 

하지만 이럴 경우 object() 이후에 작성되어 있는 유효성 검증 코드를 무시하는 상황이 발생했었습니다.

 

그래서 object() 대신 mixed()를 사용을 했었고 생각했던 것과 동일한 유효성 검증을 할 수 있었습니다.

 

 

 

왜 mixed를 사용해야 될까에 대해 궁금해서 yup github을 참고해봤는데 다음과 같은 글을 볼 수 있었습니다.

 

For unknown/any types use mixed()

알 수 없는/모든 유형의 경우 mixed() 사용

 

 

 

위에서 obj의 interface type 같은 경우는 yup에서 명확하게 확인할 수 없는 데이터 타입이기 때문에 object() 대신 mixed()를 사용해야 되는 건가라는 생각을 할 수 있던 내용이었습니다.

 

하지만 단순히 저의 추측이기 때문에 이 내용에 대해서는 너무 신뢰하시면 안 될 것 같습니다.

 

 

 

 

 

 

 

 

이상으로 제가 사용해본 yup 조건절들에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글