📌 React Router 라이브러리
- 프로덕션용으로 사용 가능한 싱글 페이지 앱을 구현(SPA)하기 위한 React Router 라이브러리
- URL이 변경돼도 싱글페이지로 렌더링 하는 기능을 지원
☑️ 데이터 가져오기 (Loader)
⏩ loader() 함수
: 특정 라우트가 렌더링되기 전에 데이터를 비동기적으로 로드하는 데 사용
- 서버에서 데이터를 가져오거나 비동기 작업을 처리한 후 페이지를 렌더링할 수 있도록 한다
- loader()는 특정 라우트가 활성화되기 전에 필요한 데이터를 미리 로드한다.
➡︎ 페이지가 로드될 때 필요한 데이터가 이미 준비되어 있어 사용자에게 빠르게 콘텐츠를 제공한다. - 특징
- 비동기 데이터 로딩
: loader()는 비동기 함수를 지원하여, 데이터를 비동기적으로 가져올 수 있다. - 라우트 기반 데이터 로딩
: 각 라우트에 대해 데이터를 로딩할 수 있어, 페이지별로 필요한 데이터를 분리하여 관리할 수 있다. - 로딩 상태 처리
: 로딩 중인 상태를 처리할 수 있으며, 로딩 중 또는 에러 상태를 UI에서 표시할 수 있다.
- 비동기 데이터 로딩
1. 라우트에 loader 설정
- createBrowserRouter 또는 createRoutesFromElements에서 loader 함수를 정의한다.
- 각 라우트에 loader를 추가하여 데이터를 로드한다.
const router = createBrowserRouter([
{
element: <Teams />,
path: "teams",
// 직접 정의된 loader 함수
loader: async () => {
return fakeDb.from("teams").select("*");
},
children: [
{
element: <Team />,
path: ":teamId",
// 직접 정의된 loader 함수
loader: async ({ params }) => {
return fetch(`/api/teams/${params.teamId}.json`);
},
},
],
},
]);
⏩ useLoaderData() 훅
: (단일) 라우트의 loader() 함수에서 로드된 데이터를 컴포넌트에서 사용할 수 있게 해준다.
- useLoaderData()를 통해 컴포넌트는 라우트가 렌더링될 때 미리 로드된 데이터를 쉽게 접근할 수 있다.
- useLoaderData()는 Router의 loader 함수로부터 반환된 데이터를 가져온다.
➡︎ loader 함수는 라우트가 활성화될 때 비동기적으로 데이터를 로드하여 반환한다. - 특징
- 데이터 접근
: loader 함수에서 반환된 데이터를 컴포넌트에서 간편하게 접근할 수 있다. - 비동기 데이터 처리
: loader와 함께 사용되어 비동기 데이터를 미리 로드하고, 컴포넌트에서 이 데이터를 쉽게 사용할 수 있게 해준다. - 라우트 의존성
: useLoaderData()는 현재 활성화된 라우트의 데이터를 가져오므로, 라우트와 관련된 데이터 로딩을 중앙집중식으로 관리할 수 있다.
- 데이터 접근
1. useLoaderData() 불러오기
import { useLoaderData } from 'react-router-dom';
2. useLoaderData() 훅을 사용하여 현재 라우트에서 로드된 데이터를 컴포넌트에서 사용하기
const teams = useLoaderData();
예제 1. 데이터 로딩과 컴포넌트 렌더링을 분리하여 관리
- loader 함수로 pb.collection('notes').getList(1, 10)을 호출하여 'notes' 컬렉션에서 1페이지에 10개의 노트를 가져온다.
- useLoaderData() 훅을 호출하여 라우트의 loader에서 반환된 데이터를 가져온다.
- data에서 items 배열을 추출하여, 각 아이템의 id와 title을 사용해 리스트를 렌더링한다.
각 리스트 항목에는 Link 컴포넌트를 사용하여 클릭 시 /notes/${id} 경로로 이동
import pb from '@/api/pb';
import { Link, useLoaderData } from 'react-router-dom';
export function Component() {
// useLoaderData() 훅을 호출하여 라우트의 loader에서 반환된 데이터를 가져온다.
const data = useLoaderData();
return (
<section id="page">
<div className="learn">
<h1>노트 리스트</h1>
<p>노트 목록을 화면에 렌더링 합니다.</p>
<ul>
{/* useLoaderData()를 통해 가져온 데이터 */}
{data.items.map(({ id, title }) => (
<li key={id}>
<Link to={`/notes/${id}`}>{title}</Link>
</li>
))}
</ul>
</div>
</section>
);
}
// eslint-disable-next-line react-refresh/only-export-components
export const loader = async () => {
const response = await pb.collection('notes').getList(1, 10);
return response;
};
// API로부터 받은 응답 데이터를 반환한다.
// 반환된 데이터는 useLoaderData()를 통해 컴포넌트에서 접근할 수 있게 된다.
⏩ useRouteLoaderData() 훅
: 특정 라우트의 loader() 함수에서 반환된 데이터를 가져오는 데 사용
- 중첩 라우트 혹은 복잡한 라우트 구조에서 특정 라우트의 데이터를 명시적으로 가져올 수 있다.
- 특징
- 명시적 데이터 접근
: 전체 라우트 트리 내에서 특정 라우트의 로더 데이터를 명시적으로 가져올 수 있다. - 중첩 라우트 지원
: 중첩된 라우트에서 상위 또는 특정 라우트의 데이터를 쉽게 가져올 수 있다.
- 명시적 데이터 접근
1. useRouteLoaderData() 불러오기
import { useRouteLoaderData } from 'react-router-dom';
2. 라우터(router)에 id 설정하기
const router = createBrowserRouter([
{
path: 'users', // 경로 설정
element: <UsersPage />,
loader: usersLoader, // 로더 설정
id: 'users', // 라우트 ID 설정
],
},
]);
3. useRouteLoaderData()를 사용하여 특정 라우트에서 로드된 데이터 가져오기
const users = useRouteLoaderData('라우트의 ID 또는 키');
예제 1. 사용자 목록 컴포넌트
// App 컴포넌트
const router = createBrowserRouter([
{
path: 'users',
element: <UsersPage />,
loader: usersLoader, // 사용자 목록을 로드하는 로더
id: 'users', // 라우트 ID 설정
children: [
{
path: ':userId',
element: <UserDetailPage />,
loader: userLoader, // 특정 사용자 정보를 로드하는 로더
id: 'user', // 라우트 ID 설정
},
],
},
]);
// UsersPage 컴포넌트
function UsersPage() {
// App.js에서 경로(path)에서 정의한 id를 넣어서 라우트에서 로드된 데이터 가져오기
const users = useRouteLoaderData('users');
return (
<div>
<h1>사용자 목록</h1>
<ul>
{users.map(user => (
<li key={user.id}>
{/* 사용자 상세 페이지로 이동하는 링크 */}
<Link to={`/users/${user.id}`}>{user.name}</Link>
</li>
))}
</ul>
</div>
);
}
✨ useLoaderData() vs useRouteLoaderData()
useLoaderData() | useRouteLoaderData() | |
목적 | 현재 활성화된 라우트의 데이터에 접근 |
특정 라우트 ID의 데이터에 명시적 접근 |
용도 | 단일 라우트에서 데이터 로딩 및 사용 |
중첩 라우트나 복잡한 라우트 구조에서 특정 라우트 데이터 접근 |
데이터 접근 범위 |
현재 활성화된 라우트의 데이터만 접근 가능 | 지정한 특정 라우트 ID의 데이터 접근 가능 |
제한 | 현재 라우트의 데이터만 사용 가능 | 특정 라우트 ID에 대한 데이터 접근 필요 |
⏩ json() 함수
: 서버로부터 받아온 응답을 JSON 형태로 변환하여 반환한다.
- 서버로부터의 응답을 JSON 형식으로 변환한다.
- loader 함수와 함께 사용한다.
1. 불러오기
import { json } from "react-router-dom";
2. loader 함수에 사용하기
const loader = async () => {
const data = getSomeData();
return json(data);
};
예제) 사용자 목록을 로드하는 loader 함수
const usersLoader = async () => {
const response = await fetch('/api/users'); // 서버로부터 데이터 요청
if (!response.ok) {
throw new Error('네트워크 응답에 문제가 있습니다.');
}
return json(await response.json()); // JSON으로 변환하여 반환
};
☑️ 데이터 업데이트 (Action)
⏩ action() 함수
: 특정 라우트에서 사용자 상호작용(ex. 폼 제출)으로 발생한 데이터를 서버로 전송하고, 서버의 응답을 처리하는 함수
- 폼 제출 또는 특정 사용자 작업에 대한 서버 측 작업을 처리
- 사용자의 입력을 서버로 전송하고, 서버에서 받은 응답을 바탕으로 클라이언트의 상태를 업데이트하는 데 유용하다.
- 일반적으로 라우트에서 데이터를 생성, 수정, 삭제하는 작업을 처리한다.
- 특징
- 서버 상호작용
: 클라이언트의 폼 제출이나 기타 작업을 서버로 전송하고, 서버의 응답을 처리한다. - 라우트 연결
: 라우트와 연결되어, 해당 라우트에서의 특정 작업(예: POST 요청)을 처리한다. - 비동기 작업 지원
: 비동기 요청을 지원하여, 서버와의 데이터 전송 및 응답 처리를 비동기적으로 수행할 수 있다.
- 서버 상호작용
1. action() 함수 정의하기
- 서버와의 상호작용을 정의하는 함수
- 이 함수는 비동기적으로 데이터를 처리할 수 있다.
export const action = async ({ request }) => {
const formData = new URLSearchParams(await request.text()); // 폼 데이터 가져오기
const name = formData.get('name'); // 'name' 필드의 값 추출
// 서버로 데이터 전송
const response = await fetch('/api/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
});
if (!response.ok) {
throw new Error('서버에 문제가 발생했습니다.');
}
return { success: true }; // 성공 응답 반환
};
2. 라우터에 action 속성 추가하기
- 특정 라우트에 action 속성을 추가하여, 해당 라우트의 action() 함수를 설정
const router = createBrowserRouter([
{
path: '/',
element: <FormPage />,
action: action, // 액션 함수 설정
},
{
path: '/success',
element: <SuccessPage />,
},
]);
3. 폼 제출 및 데이터 처리하기
- 폼을 제출하거나 작업을 수행하면, action() 함수가 호출된다.
- 서버로부터의 응답을 처리하여, 클라이언트 상태를 업데이트한다.
// FormPage.js
function FormPage() {
const navigate = useNavigate();
// 폼 제출 핸들러
const handleSubmit = (event) => {
event.preventDefault(); // 폼의 기본 제출 동작 방지
navigate('/success'); // 성공 페이지로 이동
};
return (
<div>
<h1>폼 제출</h1>
<Form method="post" action="/" onSubmit={handleSubmit}>
<label>
이름:
<input type="text" name="name" required />
</label>
<button type="submit">제출</button>
</Form>
</div>
);
}
export default FormPage;
// SuccessPage.js
function SuccessPage() {
return (
<div>
<h1>제출 성공!</h1>
<p>폼이 성공적으로 제출되었습니다.</p>
</div>
);
}
export default SuccessPage;
⏩ <Form> 컴포넌트
: 폼을 HTML 폼 요소처럼 쉽게 다룰 수 있게 해 주며, 라우터와 함께 사용할 때 폼 제출을 처리하는 데 유용
- HTML <form> 요소와 유사하지만, 폼 제출 시 페이지 이동과 같은 라우팅 관련 동작을 쉽게 처리할 수 있다.
- 특징
- 라우팅 통합
: 폼 제출 시 자동으로 라우트 이동을 처리할 수 있다. - method 및 action 속성
: HTTP 메서드(GET, POST 등)와 요청 URL을 설정할 수 있다. - 폼 상태 관리
: React Router와 통합되어 폼 상태를 관리하고, 제출 후 리디렉션을 쉽게 처리할 수 있다. - 폼 제출 자동 처리
: 폼 제출을 처리하고, 성공적으로 제출 후 라우트를 업데이트할 수 있다.
- 라우팅 통합
- 속성
- method
: 폼 데이터를 서버로 전송할 때 사용할 HTTP 메서드를 지정
- "get" : 폼 데이터를 URL의 쿼리 문자열로 전송.
일반적으로 데이터를 조회할 때 사용한다. - "post" : 폼 데이터를 요청 본문에 포함하여 전송.
데이터 생성, 수정, 삭제 등 서버의 상태를 변경하는 요청에 사용된다.
- "get" : 폼 데이터를 URL의 쿼리 문자열로 전송.
- action
: 폼 데이터를 전송할 URL을 지정
- 속성값: URL 문자열 (절대 경로 또는 상대 경로)
- 기본값: 현재 URL (window.location.pathname)
- target
: 폼 제출 시 결과를 표시할 브라우저 창 or 프레임 지정
- "_self": 현재 창에서 결과 표시
- "_blank": 새 창 또는 탭에서 결과 표시
- "_parent": 부모 프레임에서 결과 표시
- "_top": 최상위 프레임에서 결과 표시
- enctype
: 폼 데이터를 인코딩할 방식을 지정
- "application/x-www-form-urlencoded": 폼 데이터가 URL 인코딩된 쿼리 문자열 형태로 전송된다. (기본값)
- "multipart/form-data": 파일 업로드와 같은 복잡한 데이터 전송을 위해 사용된다.
- "text/plain": 폼 데이터가 텍스트 형태로 전송된다.
- method
<!-- method -->
<Form method="post" action="/submit">
<input type="text" name="name" />
<button type="submit">Submit</button>
</Form>
<!-- action -->
<Form method="post" action="/api/submit">
<input type="text" name="name" />
<button type="submit">Submit</button>
</Form>
<!-- target -->
<Form method="post" action="/api/submit" target="_blank">
<input type="text" name="name" />
<button type="submit">Submit</button>
</Form>
<!-- enctype -->
<Form method="post" action="/api/upload" enctype="multipart/form-data">
<input type="file" name="file" />
<button type="submit">Upload</button>
</Form>
1. <Form> 컴포넌트 만들기
- Form 가져오기
- <Form> 컴포넌트를 사용하여 폼을 정의하고, method, action, onSubmit 등의 속성을 설정한다.
import { Form } from "react-router-dom";
function NewEvent() {
return (
<Form method="post" action="/events">
<input type="text" name="title" />
<input type="text" name="description" />
<button type="submit">Create</button>
</Form>
);
}
2. 폼 제출 처리
- 폼 제출 후 서버와 상호작용하거나 페이지를 업데이트하려면, action 함수와 서버 측 핸들러를 설정해야 한다.
➡︎ 폼 제출 시 action 속성에 설정된 URL로 요청이 전송된다.
➡︎ 이 때, method 속성을 통해 요청의 타입(POST, GET 등)을 지정할 수 있다.
예제
// App.js
const router = createBrowserRouter([
{
path: '/events',
element: <NewEvent />,
action: action, // 액션 함수 설정
},
{
path: '/success',
element: <SuccessPage />,
},
]);
// action.js
export const action = async ({ request }) => {
const formData = new URLSearchParams(await request.text()); // 폼 데이터 가져오기
const title = formData.get('title');
const description = formData.get('description');
// 서버로 데이터 전송
const response = await fetch('/api/events', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, description }),
});
if (!response.ok) {
throw new Error('서버에 문제가 발생했습니다.');
}
return { success: true }; // 성공 응답 반환
};
⏩ redirect() 함수
: 사용자가 특정 작업을 완료한 후 다른 페이지로 자동으로 이동시키는 함수
- 서버나 클라이언트에서 페이지를 다른 URL로 리디렉션할 때 사용
- 주로 loader()나 action()에서 사용된다.
- 특징
- 간편한 리디렉션
: URL 경로를 간단히 지정하여 클라이언트를 다른 경로로 이동시킬 수 있다. - 비동기 작업 후 사용
: loader나 action 함수에서 비동기 작업(ex. 서버 요청) 후 리디렉션할 때 유용하다. - 라우터와 통합
: React Router와 원활하게 통합되어, 페이지 이동을 쉽게 처리할 수 있다.
- 간편한 리디렉션
1. loader() / action()에서 redirect 함수 사용하기
- redirect 함수를 가져온온다.
- loader 또는 action 함수 내에서 특정 조건을 만족하면, redirect() 함수를 호출하여 페이지를 이동시킨다.
import { redirect } from "react-router-dom";
const loader = async () => {
const user = await getUser();
if (!user) {
return redirect("/login");
}
return null;
};
⏩ useSubmit() 훅
: 폼 제출을 직접적으로 제어할 수 있는 기능
- <Form> 컴포넌트의 submit 이벤트를 프로그램적으로 트리거할 때 유용하며, 폼 제출을 직접적으로 제어할 수 있다.
- 폼 제출을 프로그래밍적으로 제어할 수 있는 함수 객체를 반환
➡︎ 이 함수 객체를 사용하면 폼 데이터를 비동기적으로 제출하고, 그 결과에 따라 후속 처리를 할 수 있다. - 주로 폼이 동적으로 생성되거나 사용자 인터페이스와 직접 상호작용할 때 유용하다.
- 특징
- 비동기 제출
: 폼 데이터를 비동기적으로 제출할 수 있다. - 동적 폼 제어
: 사용자 상호작용에 따라 폼을 직접 제어하고 제출할 수 있다. - 폼 상태 관리
: 제출 후 응답에 따라 상태를 관리하거나 다른 동작을 수행할 수 있다.
- 비동기 제출
- 속성
- method
: 폼 데이터를 서버로 전송할 때 사용할 HTTP 메서드를 지정
- POST, GET, PUT, DELETE
- POST, GET, PUT, DELETE
- action
: 폼 데이터를 전송할 URL을 지정
- 속성값: URL 문자열 (절대 경로 또는 상대 경로)
- 기본값: 현재 URL (window.location.pathname)
- enctype
: 폼 데이터를 인코딩할 방식을 지정
- "application/x-www-form-urlencoded": 폼 데이터가 URL 인코딩된 쿼리 문자열 형태로 전송된다. (기본값)
- "multipart/form-data": 파일 업로드와 같은 복잡한 데이터 전송을 위해 사용된다.
- "text/plain": 폼 데이터가 텍스트 형태로 전송된다.
- replace
: 현재 페이지의 히스토리 기록을 대체할지 여부를 설정
- true ➡︎ 현재 페이지의 히스토리를 새로운 페이지로 대체
- false ➡︎ 새로운 페이지가 히스토리에 추가
- headers
: HTTP 요청 헤더를 설정
- 제출 요청에 포함될 HTTP 헤더를 설정한다.
- 서버에서 요구하는 특정 헤더를 추가할 수 있다.
- method
submit(formData, {
method: 'post', // HTTP 메서드 설정
action: '/api/submit', // 폼 데이터 전송 URL 설정
encType: 'application/x-www-form-urlencoded', // 인코딩 방식 설정
replace: true, // 현재 페이지의 히스토리 대체
headers: { 'Authorization': 'Bearer token' } // HTTP 헤더 설정
});
1. useSubmit() 훅 가져오기
import { useSubmit, Form } from "react-router-dom";
2. useSubmit() 사용하여 객체 생성하기
let submit = useSubmit();
3. <Form>에서 폼 데이터 준비하기
- 폼 데이터를 수집하거나 생성
<Form
onChange={(event) => {
submit(event.currentTarget);<!-- 폼 데이터 준비 -->
}}
>
<input type="text" name="search" />
<button type="submit">Search</button>
</Form>
3. submit() 함수 호출
- submit 함수를 호출하여 폼 데이터를 제출
- 제출 후 결과를 처리
submit(formData, { method: 'post', action: '/api/submit' });
예제 1) 폼(Form) 컴포넌트
import React from 'react';
import { useSubmit } from 'react-router-dom';
function FormComponent() {
const submit = useSubmit(); // useSubmit 훅 사용
const handleSubmit = (event) => {
event.preventDefault(); // 기본 폼 제출 동작 방지
// 폼 데이터 생성
const formData = new FormData(event.target);
// submit 함수 호출
submit(formData, { method: 'post', action: '/api/submit' });
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
이름:
<input type="text" name="name" required />
</label>
</div>
<div>
<label>
이메일:
<input type="email" name="email" required />
</label>
</div>
<button type="submit">제출</button>
</form>
);
}
export default FormComponent;
예제 2) 폼(Form) 컴포넌트
import { useSubmit, Form } from "react-router-dom";
function SearchField() {
let submit = useSubmit();
return (
<Form
onChange={(event) => {
submit(event.currentTarget);
}}
>
<input type="text" name="search" />
<button type="submit">Search</button>
</Form>
);
}
import { useSubmit, useLocation } from "react-router-dom";
import { useEffect } from "react";
function AdminPage() {
useSessionTimeout();
return <div>{/* ... */}</div>;
}
function useSessionTimeout() {
const submit = useSubmit();
const location = useLocation();
useEffect(() => {
const timer = setTimeout(() => {
submit(null, { method: "post", action: "/logout" });
}, 5 * 60_000);
return () => clearTimeout(timer);
}, [submit, location]);
}
☑️ 폼 검증 (Form Validation)
⏩ useActionData()
: 폼 제출이나 특정 액션이 발생한 후 서버에서 반환된 데이터를 컴포넌트에서 접근할 수 있도록 해준다.
- 주로 Router의 action 함수와 함께 사용된다.
- 특징
- 폼 제출 후 결과 처리
: 사용자가 폼을 제출하면, action 함수가 서버로 데이터를 전송하고, 그 결과를 받아온다.
useActionData는 이 데이터를 가져와 컴포넌트 내에서 활용할 수 있게 해준다. - 서버에서 발생한 오류 처리
: 서버로부터 오류 메시지를 받아와 사용자에게 표시할 때 유용한다.
- 폼 제출 후 결과 처리
1. action 함수 정의
import { redirect } from 'react-router-dom';
export async function formAction({ request }) {
const formData = await request.formData(); // 폼 데이터를 가져옴
const data = Object.fromEntries(formData); // 폼 데이터를 객체로 변환
// 서버로 데이터 전송 또는 비즈니스 로직 처리
const response = await fakeApiRequest(data);
// 성공 시 리다이렉트, 실패 시 오류 메시지 반환
if (response.ok) {
return redirect('/success');
} else {
return { error: 'Something went wrong!' };
}
}
2. 라우터 설정에서 action 함수 연결
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import MyFormComponent from './components/MyFormComponent';
import { formAction } from './actions/formAction';
const router = createBrowserRouter([
{
path: '/submit',
element: <MyFormComponent />,
action: formAction, // 라우트에 action 함수 연결
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
3. 폼 컴포넌트에서 useActionData 사용
import { useActionData, Form } from 'react-router-dom';
function MyFormComponent() {
const actionData = useActionData(); // action 함수가 반환한 데이터 가져오기
return (
<div>
<h1>Submit Form</h1>
<Form method="post" action="/submit"> {/* formAction이 호출될 경로 */}
<label>
Name:
<input type="text" name="name" />
</label>
<button type="submit">Submit</button>
</Form>
{/* actionData가 존재하면 오류 메시지 출력 */}
{actionData?.error && <p style={{ color: 'red' }}>{actionData.error}</p>}
</div>
);
}
export default MyFormComponent;
4. Form 컴포넌트 설정
<Form method="post" action="/submit">
{/* 폼 필드들 */}
</Form>
5. useActionData()를 통해 반환된 데이터 처리
- useActionData()를 사용해 action 함수가 반환한 데이터를 컴포넌트에서 접근한다.
- 이 데이터를 활용해 UI를 업데이트하거나 사용자에게 피드백을 제공할 수 있다.