리액트 input값 validate하기

hyun·2021년 11월 16일
6

React

목록 보기
2/3

이 포스트에서 다룰 내용

외주 개발을 하면서, input값이 비어 있거나 원하는(유효한) 형식이 아닐 경우 에러메시지를 띄우고, 저장을 못하게 막는 기능을 구현했다. react-use-form 과 같은 라이브러리를 사용하기엔 이미 작성된 코드에 끼워넣기가 굉장히 복잡했고, 프로젝트가 무거워지는 것을 원하지 않았기 때문에 커스텀해서 만들어 보기로 했다.

기존 코드 상황

StudentModify
  ㄴ index.js
  ㄴ StudentModifyContainer.js
  ㄴ StudentModifyPresenter.js

StudentModify는 사진의 모달을 구현한 코드이다. 컨벤션에 따라 Container&Presenter 패턴의 디렉토리 구조를 구성했다.

새 학생 등록 로직은 간략히 정리하자면 다음과 같다.

  1. Presenter 에서 입력받은 Input값을 저장하기 버튼을 눌러서 ContainerhandleSubmit에 전달한다.
// StudentModifyPresenter.js
<SubmitButton onClick={handleSubmit}> 저장하기 </SubmitButton>
  1. ContainerhandleSubmit에서는newStudent 라는 새 객체에 담아 DB에 저장한다.
// StudentModifyContainer.js
const handleSubmit = () => {
    const newStudent = {
      name: inputName.current.value,
      school: inputSchool.current.value,
      phone: inputPhone.current.value,
      parents: inputParents.current.value,
      category,
      grade,
      subjects,
      officeHours
    };
 
  // 이 밑에서는 api를 호출해 DB에 newStudent를 저장해준다.
}

세팅

유효하지 않은 값이 들어왔을 때 각각 다른 에러메시지를 출력해주고 싶다.
예를 들면

  • 이름 필드가 비어 있을때는 "이름을 입력해주세요" 에러 메시지 출력
  • 전화번호 형식이 000-0000-0000가 아닐 때에는 "전화번호 형식이 잘못되었습니다" 에러 메시지 출력

이렇게 원하는 경우마다 다른 에러메시지를 출력해 주기 위해 errors라는 객체 state를 정의하고 여기다가 담을 것이다.

// StudentModifyContainer.js
import React, { useState } from 'react';
const [errors, setErrors] = useState(new Object());

validate 설계

StudentModifyContianer.js안에 있는 validate 가 하는 일은 다음과 같다.

  1. validate에서 newStudent를 key별로 검사한다.
    1-1. 유효하지 않은 value가 있다면 errors에 넣어준다.
  2. StudentModifyPresenter로부터 newStudent 객체를 받는다.
  3. errors가 비어있다면, 모든 key가 유효하다는 뜻이므로 handleSubmit을 호출하여 새 학생을 DB에 저장한다.

validate 구현

설계에서 생각해 둔 대로 구현해본다.

// StudentModifyContainer.js
const validate = () => {
  const temp_errors = {};
  const regPhone = /^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$/;
  const newStudent = {
    name: inputName.current.value,
    school: inputSchool.current.value,
    phone: inputPhone.current.value,
    parents: inputParents.current.value,
    category,
    grade,
    subjects,
    officeHours
  };
  
  // newStudent의 모든 key에 대해서
  for (var key in newStudent){
    // newStudent[key], 즉 key에 해당하는 value가 없다면
    if(newStudent[key].length === 0 || newStudent[key] === undefined){
      if (key === 'name') {
          temp_errors[key] = '학생 이름이 없습니다.';
        } else if (key === 'school') {
          temp_errors[key] = '학교가 없습니다.';
        } else if (key === 'phone') {
          temp_errors[key] = '학생 연락처가 없습니다.';
        } else if (key === 'parents') {
          temp_errors[key] = '부모님 연락처가 없습니다.';
        } else if (key === 'category') {
          temp_errors[key] = '계열이 없습니다.';
        } else if (key === 'grade') {
          temp_errors[key] = '학년이 없습니다.';
        } else if (key === 'subjects') {
          temp_errors[key] = '수강과목이 없습니다.';
        } else if (key === 'officeHours') {
          temp_errors[key] = '등원일이 없습니다.';
    	}
    }
  }
}

유효하지 않은 값을 찾을 때마다 state를 업데이트할 필요는 없으므로,temp_errors라는 일반 변수에 저장했다가 마지막에 한 번만 state를 업데이트 할 것이다. 전화번호를 검사할 정규식을 정의했다.
Presenter로부터 넘어온 input값으로 newStudent를 구성하고, newStudent의 key를 반복문으로 돌며 input값이 없는 경우를 확인했다.

이어서 for문 안에서 3개의 if문을 추가한다.

if (key === 'subjects') {
  for (var i = 0; i < newStudent[key].length; i++) {
    if (!newStudent[key][i].teacher) {
      temp_errors[key] = newStudent[key][i].name + '과목의 선호강사가 없습니다.';
    }
  }
}

// 전화번호가 입력되었을 때 형식 검사 
if (key === 'phone' && newStudent[key].length != 0 && regPhone.test(newStudent[key]) === false) {
  temp_errors[key] = '학생 연락처가 형식에 맞지 않습니다.';
}

if (key === 'parents' && newStudent[key].length != 0 && regPhone.test(newStudent[key]) === false) {
  temp_errors[key] = '부모님 연락처가 형식에 맞지 않습니다.';
}

if문의 조건으로 newStudent[key].length != 0 코드를 쓴 이유는, phone에 대해서 두 가지 에러가 있을 수 있기 때문이다. 전화번호가 입력되지 않은 경우 / 전화번호 형식이 잘못된 경우이다. 한 가지 에러메시지만 출력되기를 원하기 때문에 입력된 이후에 정규식 검사를 했다(이렇게 하지 않으면 전화번호 필드에 아무것도 입력하지 않았는데 전화번호 형식이 틀렸다는 에러메시지가 뜨더라).

마지막으로, for문의 바깥에서 2개의 if문을 추가한다.

// 에러가 없으면 handleSubmit
if (Object.keys(temp_errors).length === 0 && temp_errors.constructor === Object) {
  handleSubmit(newStudent); // newStudent를 DB에 등록
} else {
  setErrors({ ...temp_errors });
}

temp_errors가 빈 객체인지를 검사하고, 비어 있다면 에러가 없이 모두 유효한 값이라는 뜻이므로 DB에 저장한다. 그렇지 않다면 errors state에 temp_errors를 넣어준다. 이 errorsPresenter에 전달되어 에러메시지를 출력해 줄 예정이다.

에러메시지를 출력해보자

우선 validate, errorsStudentModifyPresenter.js에 props로 전달해야 한다.

// StudentModifyContainer.js

// 윗부분 코드 생략
// ...

return ( <StudentModifyPresenter validate={validate} errors={errors}/> )

export default StudentModifyContainer;

당연히 그냥 쓰면 안되고 이렇게 보낸 값을 받아줘야 validate, errorspresenter에서 사용할 수 있다. 그리고 저장하기 버튼을 눌렀을 때 validate를 호출하게끔 만들어준다. 그 다음 에러메시지를 저장하기 버튼 위에 출력해줄 것이다. 삼항 연산자를 이용해 errors가 비어 있다면 null을, 비어 있지 않다면 errors의 key를 map() 반복문으로 돌면서 에러 내용을 출력해준다.

// StudentModifyPresenter.js
const StudentModifyPresenter = ({validate, errors}) => {
  return (
    // html 코드
    // input도 여기 있겠죠?
   
    { Object.keys(errors).length === 0 
    ? null 
    : Object.keys(errors).map((key, index)=>(
        <ModalFormError> <i class="fas fa-exclamation-circle"></i> {errors[key]} </ModalFormError>  
      )) 
    }
    <SubmitButton onClick={validate}> 저장하기 </SubmitButton>
  )
}

결과


저장하기 버튼을 눌렀을 때 에러메시지가 잘 출력되는 것을 확인할 수 있다.
전화번호가 없을 땐 없다는 메시지를, 형식에 맞지 않는 값이 들어왔을 때는 형식에 맞지 않는다는 메시지를 잘 출력해낸다. 모든 필드에 유효한 값을 입력하면 저장도 잘 된다.

마치며

리액트를 이번 프로젝트에 처음 사용해봐서 컨벤션을 잘 모르기 때문에, 좋게 만든 코드인지는 확신이 없다. 그래도 누군가에겐 도움이 되기를 바라며 (적어도 내가 오래 기억하는 데에 도움이 될 것이다), 그리고 더 좋은 방법이 있거나 잘못된 부분이 있으면 댓글로 공유 부탁드리며 포스트를 마무리 한다.

profile
프론트엔드를 공부하고 있습니다.

1개의 댓글

comment-user-thumbnail
2021년 11월 26일

서윗한 서위치 쓰셈 ㅎㅎ

답글 달기