728x90
728x90

폼 데이터 처리하기 (React, React Router)

들어가며

  • 리액트(React)에서 폼(@<form>@) 데이터를 처리하는 방법을 정리해본다.

 

폼(Form, @<form>@)

개념

  • HTML에서 사용자 입력 데이터를 수집하고 서버로 전송하기 위한 요소
  • 로그인, 회원가입, 검색 등 사용자 입력이 필요한 기능을 구현할 때 사용된다.
<form action="/submit" method="POST">
  <label for="name">Name:</label>
  <input type="text" id="name" name="name" required>

  <label for="email">Email:</label>
  <input type="email" id="email" name="email" required>

  <button type="submit">Submit</button>
</form>

 

특징

  • @<form>@ 요소에서 @method@ 속성을 지정하지 않을 경우, 기본적으로 @GET@ 요청이 수행된다.
    • 이때, @<form>@ 요소 안의 @<input>@요소의 값을 URL의 쿼리 문자열에 포함하여 서버로 전송한다.
    • GET 요청이 수행되는 모습. <form> 안의 요소가 URL의 쿼리 문자열에 포함된다.
  • 속성의 값을 @POST@로 지정할 경우, 데이터를 본문에 포함하여 서버로 전송한다.
  • @<form>@ 요소의 @action@ 속성의 값에 폼을 제출할 때 데이터를 보낼 서버의 URL을 넣는다.
    • 생략할 경우, 현재 페이지의 URL로 전송된다.
  • @<form>@의 내부 요소로 @<input>@, @<button>@, @<label>@을 넣을 수 있다.
    • @<input>@ 요소의 @type@ 속성에 따라 다양한 입력 형태를 설정할 수 있다. (@text@, @password@, @email@, @checkbox@, @radio@, @file@ 등)
    • 또한 @<input>@ 요소에 @name@ 속성은 필수로 넣어줘야 하는데, 이 속성은 서버로 전송될 때 각 필드의 이름이 된다.
  • @<button>@ 요소의 @type@을 @'submit'@으로 지정할 경우(@<button type="submit"></button>@), 버튼 클릭 시 폼 제출 효과가 발생된다.
  • @<label>@ 요소는 폼 필드에 대한 설명을 제공하고, @for@ 속성(리액트에서는 @htmlFor@ 속성)을 사용하여 특정 @<input>@과 연결시킬 수 있다.

 

기본 동작

  • 사용자가 @Submit@ 버튼을 클릭하면 폼 데이터가 서버로 전송된다.
  • @action@ 속성에 지정된 URL로 폼 데이터가 전송되며, @method@에 따라 전송 방식이 결정된다.
  • 서버는 해당 데이터를 처리하고, 처리 결과를 브라우저로 반환한다.

이벤트 처리하기

  • @<form>@은 기본적으로 제출 시 페이지를 새로고침한다.
  • 하지만 아래와 같이 자바스크립트를 이용하여 이러한 기본 동작을 막을 수 있다.
const handleSubmit = (event) => {
  event.preventDefault();      // 기본 동작 중단시키기
  console.log("Form submitted!"); 
};

<form onSubmit={handleSubmit}>
  <input type="text" name="name" />
  <button type="submit">Submit</button>
</form>

 

폼 처리하기

(1) 리액트에서 폼 처리하기

  • 리액트에서는 @<form>@ 요소를 사용할 때, 주로 제어 컴포넌트 또는 비제어 컴포넌트 방식으로 폼 데이터를 처리한다.
import { Form } from 'react-router-dom';

<Form action="/submit" method="POST">
  <label htmlFor="name">Name:</label>
  <input type="text" id="name" name="name" required>

  <label htmlFor="email">Email:</label>
  <input type="email" id="email" name="email" required>

  <button type="submit">Submit</button>
</Form>

 

① 제어 컴포넌트(Controlled Component)

  • 리액트 상태(State)가 폼 입력값을 완전히 관리한다.
    • 입력  필드의 값이 항상 컴포넌트의 상태에 의해 제어되고, 상태가 업데이트될 때마다 입력 필드의 값도 업데이트 된다.
      • 입력값이 컴포넌트의 상태(@useState@)로 관리되며, 입력이 발생할 때마다 @onChange@ 이벤트를 통해 상태가 업데이트된다.
      • 입력 필드의 값(@value@)은 상태에 의해 제어되므로, 상태가 변경되면 자동으로 화면에 반영된다.
      • 컴포넌트에서 폼 데이터를 완전히 제어할 수 있어 입력 값의 검증 및 변경 로직을 쉽게 처리할 수 있다.
      • 모든 입력 필드에 대해 상태를 정의해야 하므로 폼 필드가 많을 경우 코드가 길어질 수 있다.
예제 코드
import { useState } from 'react';

function ControlledForm() {
  // 상태로 입력값 관리하기
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

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

    console.log('Name:', name);
    console.log('Email:', email);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input 
          type="text" 
          value={name}  // 업데이트한 값 표시
          onChange={(e) => setName(e.target.value)}   // 상태 업데이트
        />
      </label>
      <br />
      <label>
        Email:
        <input 
          type="email" 
          value={email}   // 업데이트한 값 표시
          onChange={(e) => setEmail(e.target.value)}  // 상태 업데이트
        />
      </label>
      <br />
      <button type="submit">Submit</button>
    </form>
  );
}

export default ControlledForm;

 

② 비제어 컴포넌트(Uncontrolled Component)

  • DOM 자체가 입력값을 관리한다.
  • 리액트의 상태와 상관없이 폼 입력의 현재 값을 직접 참조할 수 있다.
  • 이 방식에서는 @ref@를 사용하여 DOM 요소에 직접 접근한다.
  • 코드가 간결하며, 상태를 정의하지 않아도 되므로 입력 필드가 많아도 복잡하지 않다.
  • 리액트에서 DOM을 직접 제어하는 방식이 아니므로 입력 값에 대한 즉각적인 제어나 검증이 어렵다.
    • 따라서 간단한 폼 처리에 적합하다.

 

예제 코드
import { useRef } from 'react';

function UncontrolledForm() {
  // ref로 입력값 관리하기
  const nameInputRef = useRef(null);
  const emailInputRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    
    // ref를 통해 DOM에서 값 가져오기
    console.log('Name:', nameInputRef.current.value);
    console.log('Email:', emailInputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Name:
        <input type="text" ref={nameInputRef} />    // 직접 참조
      </label>
      <br />
      <label>
        Email:
        <input type="email" ref={emailInputRef} />   // 직접 참조
      </label>
      <br />
      <button type="submit">Submit</button>
    </form>
  );
}

export default UncontrolledForm;

 

 

(2) 리액트 라우터(React Router)를 이용하여 폼 처리하기

  • 리액트 라우터(React Router)에서는 @<form>@ 요소 대신 리액트 라우터의 @Form@ 컴포넌트를 사용하여 폼 제출을 처리한다.
  • @Form@ 컴포넌트는 전통적인 HTML 폼과 유사하게 동작하지만, 리액트 라우터가 폼 제출을 자동으로 처리하고, 폼 데이터를 액션(Action) 함수로 전달한다.

 

방법

  • 예제 코드를 이용하여 이해해보자.

 

파일 구조
src/
 ├── App.jsx
 ├── pages/
 │     └── Newsletter.jsx
 └── main.jsx

 

/src/App.jsx
  • 라우터에 @action@ 속성을 넣어주고, 폼 제출 시 실행 될 @action@을 지정해준다.
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Newsletter, { action as newsletterAction  } from './pages/Newsletter';

const router = createBrowserRouter([
  {
    path: "/newsletter",
    element: <Newsletter />,
    action: newsletterAction,  // 폼 제출 시 실행할 action 지정
  },
]);

function App() {
  return (
    <RouterProvider router={router} />
  );
}

export default App;

 

/src/pages/Newsletter.jsx
  • 리액트 라우터의 @Form@ 컴포넌트는 전통적인 HTML 폼과 동일하게 동작하지만, 폼을 제출하면 설정된 @action@ 함수가 실행된다.
  • @action@ 함수는 폼이 제출될 때 호출된다.
  • @request.formData()@를 통해 폼 데이터를 가져올 수 있다. 이 데이터를 @Object.fromEntries()@를 이용하여 객체(Object)로 변환하여 처리할 수 있다.
import { Form, redirect } from 'react-router-dom';

export const action = async ({ request }) => {
  const formData = await request.formData();  // 폼 데이터 가져오기
  const data = Object.fromEntries(formData);  // 데이터를 객체로 변환
  console.log(data);  // 예: { name: "John", email: "john@example.com" }

  // 폼 데이터를 처리하고, 필요한 경우 서버에 요청을 보낸다.
  const response = await fetch("https://api.example.com", { method: 'POST', body: formData });
  
  return redirect('/'); 
};

function Newsletter() {
  return (
    <div>
      <h1>Subscribe to our Newsletter</h1>
      <Form method="post">  {/* React Router의 Form 컴포넌트 */}
        <label>
          Name: 
          <input type="text" name="name" required />
        </label>
        <br />
        <label>
          Email:
          <input type="email" name="email" required />
        </label>
        <br />
        <button type="submit">Subscribe</button>
      </Form>
    </div>
  );
}

export default Newsletter;
HTML5의 required 속성을 사용하여 폼 입력값이 비어 있을 경우 폼 제출을 막을 수 있다.

 

참고

Object.fromEntries()

  • @Object.fromEntries()@는 ES2019에서 도입된 자바스크립트 메서드로, 키-값 쌍의 배열 또는 이터러블(Iterable) 객체를 객체(Object)로 변환해준다.
    • @Map@, @FormData@, @URLSearchParams@ 같은 이터러블 객체를 다룰 때 유용하다.
    • 중복된 키를 허용하지 않으며, 마지막 키의 값만 유지된다.
      • 예) @[['a', 1], ['a', 2]]@와 같이 같은 키가 여러 번 나타나면, 마지막 값인 @2@가 유지된다.
    • 비어 있는 배열이나 이터러블을 사용하면 빈 객체가 반환된다.

 

사용 예제
// 1️⃣ 배열을 객체로 변환하기
const entries = [['name', 'John'], ['age', 30], ['city', 'Zurich']];
const obj = Object.fromEntries(entries);

console.log(obj);
// { name: 'John', age: 30, city: 'Zurich' }

//  2️⃣ Map 객체를 객체로 변환하기
const map = new Map([['name', 'Jane'], ['age', 25]]);
const obj = Object.fromEntries(map);

console.log(obj);
// { name: 'Jane', age: 25 }

// 3️⃣ FormData 객체를 사용하여 폼 데이터를 객체로 변환하기
const formData = new FormData();
formData.append('username', 'JohnDoe');
formData.append('email', 'johndoe@example.com');

const obj = Object.fromEntries(formData);

console.log(obj);
// { username: 'JohnDoe', email: 'johndoe@example.com' }


// 4️⃣ URLSearchParams를 객체로 변환하기
const params = new URLSearchParams('name=John&age=30');
const obj = Object.fromEntries(params);

console.log(obj);
// { name: 'John', age: '30' }


// ☑️ 중복된 키를 허용하지 않는다.
const entries = [['a', 1], ['a', 2]];
const obj = Object.fromEntries(entries);

console.log(obj); 
// { a: 2 }

// ☑️ 비어 있는 배열이나 이터러블을 사용하면 긴 객체가 반환된다.
const obj = Object.fromEntries([]);
console.log(obj);
// {}

 

참고 사이트

 

<form> - HTML: Hypertext Markup Language | MDN

HTML <form> 요소는 정보를 제출하기 위한 대화형 컨트롤을 포함하는 문서 구획을 나타냅니다.

developer.mozilla.org

 

What are Controlled and Uncontrolled Components in React.js?

In React.js, managing form inputs and user interactions is a crucial part of building dynamic web applications. Two key concepts that developers need to understand are controlled and uncontrolled components. These concepts define how form data is ha...

www.freecodecamp.org

 

<input> – React

The library for web and native user interfaces

react.dev

 

Form | React Router

 

reactrouter.com

 

Object.fromEntries() - JavaScript | MDN

Object.fromEntries() 메서드는 키값 쌍의 목록을 객체로 바꿉니다.

developer.mozilla.org

728x90
728x90