폼 데이터 처리하기 (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
'Programming > React' 카테고리의 다른 글
[React.js] index.js로 컴포넌트(Component), 페이지(Page) 관리하기 (0) | 2024.10.01 |
---|---|
[React.js] Thunk API (Redux Toolkit) (1) | 2024.09.28 |
[React.js] 리액트 라우터(React Router) (0) | 2024.09.26 |
[React.js] 라우팅 관련 기능들 정리 (React Router) : useNavigate, useNavigation, redirect, useLocation, useParams, useHistory, Navigate (1) | 2024.09.26 |
[React.js] 무한 스크롤(Infinite Scroll), 스켈레톤(Skeleton) 효과 적용하기 (with React Query) (0) | 2024.09.23 |
[React.js] .env 파일 만들고 사용하기 (환경 변수 관리) (0) | 2024.09.23 |
[React.js] React Query Devtools (0) | 2024.09.22 |
[React.js] 코드 분할(Code Splitting) : useTransition 훅, Suspense 컴포넌트, lazy 함수 (0) | 2024.09.20 |