Form과 사용자 입력 처리

1. Form이란?

React에서의 Form은 사용자로부터 데이터를 수집하기 위한 중요한 UI 요소이다.

Form은 입력 필드의 집합으로, 주로 다음 세 가지 용도로 사용된다.

1) 사용자 입력 값 양식화
2) 입력 값 추출
3) 데이터 유효성 검증

 

이 중 값을 추출하는 것은 비교적 간단하지만, 검증 과정은 여러 가지 고려사항이 필요하다.

 

1.1. HTML Form vs React Form

HTML Form은 기본적으로 자체 상태를 관리하고 페이지를 새로고침한다.

React 애플리케이션에서는 이런 동작이 사용자 경험을 방해할 수 있으므로, JavaScript를 통해 Form의 동작을 제어한다.

React에서는 컴포넌트의 state를 "신뢰 가능한 단일 출처(single source of truth)"로 만들어 Form의 동작을 관리한다.

 

 

2. 브라우저의 기본 Form 동작

2.1. 폼 제출과 페이지 새로고침

폼 내부에 버튼을 넣으면 기본적으로 폼이 제출되고 페이지가 새로고침된다.

이는 브라우저에서 폼 내 버튼의 기본 타입이 submit이며, 이 버튼을 클릭하면 HTTP 요청이 서버로 전송되기 때문이다.

 

ex)

// 폼이 제출되고 페이지가 새로고침됨
<form>
  <input type="text" />
  <button>제출</button>  // 기본 타입은 "submit"
</form>

 

2.2. 기본 동작 방지 방법

React 애플리케이션에서는 이런 동작을 방지하기 위해 두 가지 방법을 사용할 수 있다.

 

1) 버튼 타입 명시적 지정

ex)

<form>
  <input type="text" />
  <button type="button">제출</button>  // 폼 제출 동작 없음
</form>

 

2) onSubmit 이벤트 핸들러와 preventDefault() 사용

ex)

function FormComponent() {
  const handleSubmit = (event) => {
    event.preventDefault();  // 기본 제출 동작 방지
    // 폼 데이터 처리 로직
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" />
      <button type="submit">제출</button>
    </form>
  );
}

 

이 중 두 번째 방법이 React에서 권장되는 접근법이다.

 

 

3. Form 입력 처리 방법

React에서 Form 입력을 처리하는 방법에는 세 가지 주요 접근법이 있다.

 

3.1. 제어 컴포넌트 (Controlled Components)

제어 컴포넌트는 React에서 가장 일반적인 접근법으로, Form 입력 값을 React의 state로 관리한다.

이 방식에서는 입력 요소의 값이 항상 React state에 의해 제어된다.

 

ex)

import { useState } from 'react';

function ControlledForm() {
  const [name, setName] = useState('');

  const handleChange = (event) => {
    setName(event.target.value);
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    alert(`제출된 이름: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        이름:
        <input 
          type="text" 
          value={name} 
          onChange={handleChange} 
        />
      </label>
      <button type="submit">제출</button>
    </form>
  );
}


여러 입력 필드 관리:

ex)

import { useState } from 'react';

function MultipleInputsForm() {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    message: ''
  });

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData(prevData => ({
      ...prevData,
      [name]: value  // 대괄호 표기법으로 동적 속성 업데이트
    }));
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('폼 데이터:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">이름:</label>
        <input
          type="text"
          id="name"
          name="name"  // state 객체의 키와 일치
          value={formData.name}
          onChange={handleChange}
        />
      </div>

      <div>
        <label htmlFor="email">이메일:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />
      </div>

      <div>
        <label htmlFor="message">메시지:</label>
        <textarea
          id="message"
          name="message"
          value={formData.message}
          onChange={handleChange}
        />
      </div>

      <button type="submit">제출</button>
    </form>
  );
}

 

장점:

  • 입력값에 실시간으로 접근하고 제어할 수 있다.
  • 즉각적인 유효성 검사 및 조건부 UI 렌더링이 가능하다.
  • 폼 값을 외부 상태와 쉽게 동기화할 수 있다.

단점:

  • 입력 필드가 많은 경우 state와 핸들러 함수가 복잡해질 수 있다.
  • 모든 입력 변경마다 컴포넌트가 리렌더링된다.

 

3.2. 비제어 컴포넌트 (Uncontrolled Components)

비제어 컴포넌트는 React의 ref를 사용하여 DOM 요소에 직접 접근하는 방식이다.

이 방식에서는 입력 값이 DOM 자체에 저장되고, 필요할 때만 참조한다.

 

ex)

import { useRef } from 'react';

function UncontrolledForm() {
  const nameInputRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    const name = nameInputRef.current.value;
    alert(`제출된 이름: ${name}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        이름:
        <input type="text" ref={nameInputRef} defaultValue="" />
      </label>
      <button type="submit">제출</button>
    </form>
  );
}

 

여러 입력 필드 관리:

ex)

import { useRef } from 'react';

function MultipleRefsForm() {
  const nameRef = useRef(null);
  const emailRef = useRef(null);
  const messageRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();

    const formData = {
      name: nameRef.current.value,
      email: emailRef.current.value,
      message: messageRef.current.value
    };

    console.log('폼 데이터:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">이름:</label>
        <input
          type="text"
          id="name"
          ref={nameRef}
          defaultValue=""
        />
      </div>

      <div>
        <label htmlFor="email">이메일:</label>
        <input
          type="email"
          id="email"
          ref={emailRef}
          defaultValue=""
        />
      </div>

      <div>
        <label htmlFor="message">메시지:</label>
        <textarea
          id="message"
          ref={messageRef}
          defaultValue=""
        />
      </div>

      <button type="submit">제출</button>
    </form>
  );
}

 

장점:

  • 구현이 간단하고 React state 관리가 필요 없다.
  • 입력 필드가 변경되어도 리렌더링이 발생하지 않는다.
  • 파일 입력과 같은 특수 케이스에 유용하다.

단점:

  • 실시간 입력 검증이 어렵다.
  • 각 입력마다 별도의 ref를 생성해야 한다.
  • 조건부 렌더링이나 폼 상태에 따른 UI 변경이 어렵다.
  • 값 초기화가 상대적으로 복잡하다.

 

3.3. FormData API 활용

브라우저 내장 FormData API를 활용하면 폼 데이터를 쉽게 수집할 수 있다.

이 방식은 비제어 컴포넌트와 유사하지만, ref 대신 name 속성과 FormData 객체를 사용한다.

 

ex)

function FormDataExample() {
  const handleSubmit = (event) => {
    event.preventDefault();

    const formData = new FormData(event.target);
    const name = formData.get('name');
    const email = formData.get('email');
    const message = formData.get('message');

    // 객체로 변환
    const data = Object.fromEntries(formData.entries());

    console.log('개별 필드:', { name, email, message });
    console.log('전체 데이터:', data);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">이름:</label>
        <input type="text" id="name" name="name" />
      </div>

      <div>
        <label htmlFor="email">이메일:</label>
        <input type="email" id="email" name="email" />
      </div>

      <div>
        <label htmlFor="message">메시지:</label>
        <textarea id="message" name="message" />
      </div>

      <button type="submit">제출</button>
    </form>
  );
}

 

특수 케이스 처리:

ex)

function FormDataSpecialCases() {
  const handleSubmit = (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);

    // 체크박스 그룹 값 가져오기
    const selectedFruits = formData.getAll('fruits');

    // 파일 입력 처리
    const file = formData.get('profilePic');

    console.log('선택된 과일:', selectedFruits);
    console.log('업로드된 파일:', file);
  };

  return (
    <form onSubmit={handleSubmit}>
      <fieldset>
        <legend>좋아하는 과일:</legend>
        <div>
          <input type="checkbox" id="apple" name="fruits" value="apple" />
          <label htmlFor="apple">사과</label>
        </div>
        <div>
          <input type="checkbox" id="banana" name="fruits" value="banana" />
          <label htmlFor="banana">바나나</label>
        </div>
        <div>
          <input type="checkbox" id="orange" name="fruits" value="orange" />
          <label htmlFor="orange">오렌지</label>
        </div>
      </fieldset>

      <div>
        <label htmlFor="profilePic">프로필 사진:</label>
        <input type="file" id="profilePic" name="profilePic" />
      </div>

      <button type="submit">제출</button>
    </form>
  );
}

 

장점:

  • 복잡한 폼 구조에서 데이터 수집이 간편하다.
  • 파일 업로드와 같은 특수 케이스를 쉽게 처리할 수 있다.
  • 코드가 간결해진다.

단점:

  • 실시간 입력 제어가 제한적이다.
  • 값을 초기화하거나 프로그래밍적으로 설정하는 것이 어렵다.
  • 브라우저 호환성 문제가 있을 수 있다(오래된 브라우저).

 

4. Form 검증 시점

폼 유효성 검증은 언제 수행되는지에 따라 사용자 경험이 크게 달라질 수 있다.

 

4.1. 검증 시점별 장단점

1) 키 입력마다 검증

 

장점: 즉각적인 피드백 제공
단점: 사용자가 타이핑하는 동안 오류 메시지가 계속 표시되어 사용자 경험 저하

 

2) 포커스를 잃을 때(blur) 검증

 

장점: 사용자가 입력을 완료한 후 피드백 제공
단점: 오류 메시지가 다음 입력 필드로 넘어갈 때까지 화면에 남음

 

3) 제출 시 검증

 

장점: 사용자가 폼을 완성한 후에만 오류 메시지 표시
단점: 사용자가 모든 필드를 입력한 후에야 피드백을 받음

 

 

5. Form 초기화

폼 데이터를 초기화하는 방법은 폼 처리 방식에 따라 다르다.

 

5.1. HTML 기본 방식

HTML의 기본 기능인 type="reset" 버튼을 사용할 수 있다.

 

ex)

<form>
  <input type="text" name="name" />
  <button type="reset">초기화</button>
</form>

 

5.2. 제어 컴포넌트 초기화

제어 컴포넌트에서는 state를 초기값으로 재설정한다.

 

ex)

function ControlledFormReset() {
  const initialState = {
    name: '',
    email: '',
    message: ''
  };

  const [formData, setFormData] = useState(initialState);

  const handleChange = (event) => {
    const { name, value } = event.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('폼 데이터:', formData);
    // 처리 후 초기화
    setFormData(initialState);
  };

  const handleReset = () => {
    setFormData(initialState);
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* 입력 필드들 */}
      <div>
        <button type="submit">제출</button>
        <button type="button" onClick={handleReset}>초기화</button>
      </div>
    </form>
  );
}

 

5.3. 비제어 컴포넌트 초기화

비제어 컴포넌트에서는 폼 요소나 ref를 사용해 초기화할 수 있다.

 

ex)

function UncontrolledFormReset() {
  const formRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();

    // 데이터 처리 로직

    // 폼 초기화
    formRef.current.reset();
  };

  const handleReset = () => {
    formRef.current.reset();
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="text" name="name" />
      <input type="email" name="email" />

      <button type="submit">제출</button>
      <button type="button" onClick={handleReset}>초기화</button>
    </form>
  );
}

 

5.4. FormData API 사용 시 초기화

FormData를 사용할 때는 일반적으로 HTML 폼의 reset() 메서드를 활용한다.

 

ex)

function FormDataReset() {
  const formRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    const formData = new FormData(event.target);

    // 데이터 처리 로직
    console.log(Object.fromEntries(formData));

    // 폼 초기화
    formRef.current.reset();
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <input type="text" name="name" />
      <input type="email" name="email" />

      <button type="submit">제출</button>
      <button type="button" onClick={() => formRef.current.reset()}>
        초기화
      </button>
    </form>
  );
}

'학습 > React' 카테고리의 다른 글

Redux  (0) 2025.05.09
Form Action  (0) 2025.05.02
리액트 기초 - 강의 정리 4 : fetch, 커스텀  (0) 2025.04.26
Million.js  (0) 2025.04.23
React key  (0) 2025.04.23