react-router-dom에는 loader와 action 프로퍼티가 존재한다. 이들은 라우터가 렌더링 되기 전이나 요청을 보낼 때 사용한다.
이전 포스트에 이어서 작성할 예정이다.
loader는 라우터가 렌더링 되기 전에 실행되는 함수를 등록한다.
간단하게 아래와 같이 사용할 수 있다.
index: true,
element: <ArticlesPage />,
loader: () => {
console.log("articles!!");
},
위 코드를 이용하면 'articles' URL로 이동하면 콘솔에 "articles!!"이 출력된다. 일반적으로 loader는 라우터가 렌더링 되기 전에 서버에서 API 통신 후 응답을 받는 데이터를 가져오기 위해 사용된다.
API를 사용하기 위해서 jsonplaceholder를 간편하게 사용해보겠다. jsonplaceholder는 간단한 dummy data를 제공하는 REST API이다.
https://jsonplaceholder.typicode.com/
{
index: true,
element: <ArticlesPage />,
loader: async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);
if (!response.ok) {
// ...
} else {
const resData = await response.json();
return resData;
}
},
},
참고로 https://jsonplaceholder.typicode.com/posts/1 는 아래를 반환한다.
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
이제 이를 ArticlesPage에서 사용할 수 있다. API를 "https://jsonplaceholder.typicode.com/posts" 로 바꾸고 코드를 수정하면,
import { useLoaderData } from "react-router-dom";
function ArticlesPage() {
const posts = useLoaderData();
return (
<div>
<p>ArticlesPage</p>
{(posts) => (
<ol>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ol>
)}
</div>
);
}
export default ArticlesPage;
useLoaderData()를 사용하여 라우터의 loader에서부터 데이터를 가져올 수 있다.

그런데 라우터에 loader 함수를 정의하니까 지저분한 감이 없지 않아 있다. 이때, loader 함수는 실제 loader가 필요한 컴포넌트에 넣는 것이 일반적이다. 이 경우에는 ArticlesPage 컴포넌트에 아래와 같이 loader를 넣을 수 있다.
...
export default ArticlesPage;
export async function loader() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
console.log(response);
if (response.status !== 200) {
throw new Response(JSON.stringify({ message: "POST 불러오기 실패" }), {
status: 500,
});
} else {
const resData = await response.json();
return resData;
}
}
이제 App.js에서 함수를 불러오기만 하면 된다.
import ArticlesRootLayout from "./pages/ArticleRootLayout";
...
{
index: true,
element: <ArticlesPage />,
loader: ArticlesLoader,
},
이렇게 간단하게 사용할 수 있다.
만약 fetch 중 에러가 발생하면?
ErrorPage 컴포넌트에서 에러를 받아올 수 있다.
import { useRouteError } from "react-router-dom";
function ErrorPage() {
const error = useRouteError();
let message = "오류 발생";
if (error.status === 500) {
message = JSON.parse(error.data).message;
}
if (error.status === 404) {
message = "잘못된 주소";
}
return (
<>
<h1>Error</h1>
<p>{message}</p>
</>
);
}
export default ErrorPage;
useRouteError 훅을 이용하면 에러가 발생할 경우 해당 에러의 정보를 가져올 수 있다. 그리고 간단하게 에러의 상태에 따라 메시지를 출력할 수 있다.
action는 라우터가 요청을 보낼 때 실행되는 함수를 등록한다.
import { Form, redirect } from "react-router-dom";
function NewArticlePage() {
function submitHandler(event) {
event.preventDefault();
}
return (
<div>
<p>New Article Page</p>
<Form method="post">
<span>Title</span>
<input type="text" name="title" />
<span>Body</span>
<input type="text" name="body" />
<button>New</button>
</Form>
</div>
);
}
export default NewArticlePage;
export async function action({ request, params }) {
const data = await request.formData();
const articleData = {
title: data.get("title"),
body: data.get("body"),
};
const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
method: request.method,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(articleData),
});
if (!response.ok) {
return new Response({ message: "Article 생성 오류" }, { status: 500 });
}
return redirect("/articles");
}
...
// App.jsx
import NewArticlePage, { action as NewArticleAction } from "./pages/NewArticlePage";
{
path: "new",
element: <NewArticlePage />,
action: NewArticleAction,
},
loader와 마찬가지로 요청이 들어올 시 action 함수를 사용할 수 있다. Form 컴포넌트에서 요청을 할 경우 서버로 보내는 것이 아니라 클라이언트에 요청을 보낸다. 이 요청을 action 함수가 받아서 사용한다.
action 함수를 보면 request에서 formData를 가져온다. 이때 formData는 Form 컴포넌트 내부에 있는 name 속성이 있는 태그들을 가져올 수 있다. 이제 가져온 데이터를 서버에 보내서 POST 요청을 보낼 수 있다.
Form 컴포넌트와 redirect의 자세한 내용은 나중에 정리를 해야겠다.