Guide to Banned Word Validation with Yup

November 25, 2023

react-hook-form, yup, react로 금지어 체크하는 방법

❓ 만약 배달서비스 리뷰에 “닭볶음탕 맛있게 잘먹었어요.“ 란 리뷰를 남겼을 때

금지어 규칙에 걸렸다면 꽤 황당하지 않을 수 없다.

음탕“이란 단어가 금지어이기 때문이다.

그렇다면 금지어 체크 기준이 어떻길래 “음탕”이란 단어를 필터링 할 수 있는걸까?


금지어 체크하는 방법

react, react-hook-form, yup을 사용하여 금지어 체크를 구현해보았다.


yup

객체 스키마 유효성 검사 라이브러리로 데이터 유효성 검증을 간편하게 수행할 수 있다.

react-hook-form 공식문서에서도 yup과 조합하여 유효성을 검증하는 예제가 있다.


금지어란 단어를 금지하고 싶다.

금지어를 금지하기 위해선 “금지어” 뿐만 아니라 “금 지 어”, “금지 어”, “금 지어” 같은 띄어쓰기한 단어도 마찬가지로 금지해야한다.

우선 금지하고 싶은 모든 단어들의 경우를 배열에 담는다.

// 모조리 배열에 담아본다.
const PROHIBITED_WORDS = ["금지어", "금 지 어", "금지 어", "금 지어"];

yup의 addMethod란 메서드로 금지어 체크 custom method를 작성한다.

import * as yup from "yup";

// 정규 표현식에서 사용하기 위해 문자열의 특수 문자를 이스케이프하는 함수
const escapeRegExp = (string: string) => {
    // 정규 표현식의 특수 문자 이스케이프
    return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};

// 금지된 단어에 대한 정규 표현식 패턴을 생성하는 함수
const createPattern = (word: string) => {
    // 단어를 문자로 분리하고 각 문자를 이스케이프
    const chars = word.split("").map((c) => escapeRegExp(c));

    // 문자들을 비단어 문자(\W)가 여러 개 있을 수 있는 패턴으로 조합
    const patternString = chars.join("[\\W_]*");
    // 생성된 패턴으로 정규 표현식 객체를 생성하고 반환 (대소문자 구분 없음)
    return new RegExp(patternString, "i");
};

// yup.string에 사용자 정의 검증 메서드를 추가하는 함수
export const addProhibitedWordValidation = (prohibitedWords: string[]) => {
    // 각 금지된 단어에 대한 정규 표현식 패턴 배열 생성
    const patterns = prohibitedWords.map(createPattern);

    // yup.string에 'validateAgainstProhibitedWords'라는 사용자 정의 메서드 추가
    yup.addMethod(
        yup.string,
        "validateAgainstProhibitedWords",
        // 사용자 정의 검증을 위한 테스트 정의
        function (message: string) {
            return this.test("validate-against-prohibited-words", message, function (value: string | undefined) {
                const { path, createError } = this;
                // 값이 undefined일 경우 검증을 통과시킴
                if (!value) return true;

                // 값이 금지된 단어 패턴 중 하나와 일치하는지 확인
                const containsProhibitedWord = patterns.some((pattern) => pattern.test(value));

                // 금지된 단어가 없으면 true를 반환하고, 있으면 오류 생성
                return !containsProhibitedWord || createError({ path, message });
            });
        }
    );
};

addProhibitedWordValidation 메소드는 string[] 타입에 해당하는 모든 변수들의 유효성을 체크할 수 있는 함수이다.

만약 서비스마다 금지어 항목이 다르다면 각 서비스에 해당하는 배열을 선언 후 동일한 addProhibitedWordValidation 메소드를 사용하면 된다.


사용하고자하는 특정 컴포넌트 상위에 아래 함수에 작성한 금지어 변수를 매개변수로 받아 실행시킨다.

// component.tsx

addProhibitedWordValidation(PROHIBITED_WORDS);

const Component = () => {
    //...
};

만약 특정 form에서 금지어를 체크해야한다면 아래와 같이 yup.object를 이용하여 다른 항목들과 함께 유효성 검사를 할 수 있다.

// component.tsx

// yup으로 금지어 체크시 아래 함수 상위 실행. 사용자 정의 메서드 추가의 역할.
addProhibitedWordValidation(PROHIBITED_WORDS);

const formSchema = yup.object({
    content: yup
        .string()
        .required()
        .validateAgainstProhibitedWords("Caution: The entered content contains prohibited language."),
});

const Component = () => {
    const { register } = useForm({
        defaultValues: { content: "" },
        resolver: yupResolver(formSchema),
    });

    const handleSubmitContent: SubmitHandler<{ content: string }> = (data) => {
        // post request
    };

    return (
        <form onSubmit={handleSubmit(handleSubmitContent)}>
            <textarea {...register("content")} placeholder="Click here to write a review" />
            <button type="submit">submit</button>
        </form>
    );
};

별첨

타입스크립트 프로젝트라면 폴더 구조 내 타입을 정의하는 파일에 커스텀 타입을 추가한다.

import "yup";

declare module "yup" {
    interface StringSchema {
        // 금지어 체크를 위해 사용자 정의 메소드 사용.
        validateAgainstProhibitedWords(message: string): StringSchema;
    }
}

정리

  • yup으로 커스텀 메소드를 만든다.
  • 금지하고자 하는 단어들을 string[] 타입의 형태로 변수에 담는다.
  • 금지어 변수와 함수를 조합하여 특정 컴포넌트에서 사용한다.