React Hook Form 필드 초기화 이슈, form의 state 이해하기

현장실습 인턴을 진행하던 중, 댓글이나 답글 기능을 만들면서 React Hook Form을 사용하게 되었습니다. React Hook Form은 폼 상태 관리와 유효성 검사를 간편하게 처리할 수 있게 도와주는 라이브러리로, 불필요한 리렌더링을 최소화하기 때문에 자주 사용하게 됩니다.

예를 들어, useState를 사용한다면 input에 값을 한 글자 입력할 때 마다 리렌더링이 발생하기 때문에, form을 주로 사용합니다.

그러나 댓글을 작성하고 나서 입력란을 초기화하는 과정에서 생각하지 못한 문제가 생겼습니다. 이번 글에서는 제가 실제로 겪었던 문제의 원인과 해결 방법을 알기 쉽게 설명하려고 합니다.

React Hook Form의 기본적인 상태

React Hook Form은 폼과 필드의 상태를 효과적으로 관리하기 위해 다양한 상태를 제공합니다.

  • dirty: 필드의 값이 처음 설정된 기본값(defaultValue)과 달라졌을 때 true가 됩니다.
  • touched: 사용자가 특정 필드를 클릭하여 포커스를 맞춘 후, 다른 필드를 클릭하거나 탭을 눌러 포커스를 이동하면 true가 됩니다.
  • errors: 각 필드의 유효성 검사(validation) 실패 시, 오류 메시지를 저장하는 상태입니다.
  • isValid: 폼이 유효성 검사(validation)를 통과했는지를 나타냅니다.
  • isSubmitting: 폼이 제출 중인지 여부를 나타냅니다.
  • isSubmitted: 폼이 제출된 적이 있는지 여부를 나타냅니다.

이 상태들을 잘 이해하면 폼의 동작을 더욱 정확하게 제어할 수 있습니다.

발생했던 문제

처음에는 폼 제출 후 폼 전체를 초기화하려고 form.reset()을 사용했습니다.

const form = useForm<z.infer<typeof FormSchema>>({
  resolver: zodResolver(FormSchema),
  defaultValues: {
    text: comment.text,
    reply: "",
  },
});

const onCreateReply = async (data): Promise<void> => {
  if (data.reply !== '') {
    // 데이터 처리 로직
    form.reset();
  }
};

그런데 이상하게도 한 번 제출한 이후, 다시 값을 입력하고 제출하려고 하니 폼이 동작하지 않는 문제가 발생했습니다.

왜 이런 문제가 생겼을까?

이 문제의 핵심은 form.reset()의 동작 방식 때문이었습니다. reset() 함수는 폼 전체를 초기값으로 돌리면서 각 필드의 상태(dirty, touched 등)를 모두 초기화합니다.

그렇기 때문에 초기화한 뒤 다시 새로운 값을 입력하더라도 React Hook Form이 이를 "변경된 값"으로 인식하지 못하는 현상이 생겼습니다.

더 쉽게 이해해보면

React Hook Form은 필드의 값이 처음 설정된 기본값(defaultValue)과 달라졌을 때만 dirty 상태를 true로 만듭니다. 하지만 reset()을 사용하면 입력값이 처음 기본값과 동일한 상태로 완전히 초기화됩니다. 이후 새로 값을 입력해도, React Hook Form은 "이전에도 이 값에서 출발했으니 변경된 게 없다"고 판단하여 dirty를 false로 유지합니다.

즉, "새로 입력했다"고 생각하지만, 라이브러리 입장에서는 "변화가 없는 값이다"라고 인식하는 겁니다.

특정 필드만 초기화해야 하는 이유

대부분 댓글 기능처럼 특정 입력 필드만 초기화하고, 다른 데이터는 유지해야 할 때가 많습니다. 이럴 때 전체 폼을 초기화하면 필요하지 않은 부분까지 상태가 바뀌어 버리기 때문에 문제가 될 수 있습니다. 따라서 댓글 입력란처럼 특정 필드만 초기화하는 방식이 훨씬 효율적이고 안정적입니다.

문제 해결 방법: form.setValue 사용하기

저는 이 문제를 다음과 같이 해결했습니다.

const form = useForm<z.infer<typeof FormSchema>>({
  resolver: zodResolver(FormSchema),
  defaultValues: {
    text: comment.text,
  },
});

const onCreateReply = async (data): Promise<void> => {
  if (data.reply !== '') {
    // reply 필드만 초기화
    form.setValue('reply', '');
  }
};

이렇게 setValue를 사용하면 특정 필드의 값만 바뀌고, 내부적으로 상태도 올바르게 갱신됩니다. 따라서 다시 입력하고 제출할 때에도 문제가 없이 잘 동작하게 되었습니다.

간단히 정리하자면

  • _form.reset()*_은 폼 전체 상태를 초기화하여 이후 입력된 값도 기존 값과 비교해 "변화 없음"으로 판단할 수 있습니다.
  • _form.setValue()*_는 특정 필드 값만 초기화하면서 상태를 정확하게 업데이트해줍니다.

다음 방법도 가능하지만, setValue가 더 직관적이고 관리하기 편리합니다.

form.resetField('reply', { defaultValue: '' });

마치며

보통 form을 로그인 등 submit 후 페이지를 새로고침 시키는, 1회적인 용도로 많이 사용을 해와서 이런 문제가 발생할 것이라고 전혀 생각하지 못했습니다.

덕분에 react hook form에서 어떤 상태를 가질 수 있는지도 공부할 수 있었고, 여러모로 도움 됐던 문제였던 것 같아요.

React Hook Form을 쓰다가 비슷한 문제를 겪는 분들에게 이번 글이 조금이나마 도움이 되었으면 좋겠습니다.