FE develop/React

React Router Dom - (8) useLoaderData로 데이터 가져오기

hoj0806 2025. 4. 5. 05:47

이번 포스트에서는 React Router의 useLoaderData 훅을 사용해 데이터를 패칭하고 관리하는 방법에 대해 알아보겠습니다. 기존의 useEffect와 useState를 사용한 방식보다 간단하고 선언적인 방식으로 데이터를 다룰 수 있어, 더 깔끔한 코드 작성이 가능합니다.

 

 

기존 방식: useEffect로 데이터 패칭

아래는 EventPage 컴포넌트에서 useEffect를 사용해 이벤트 데이터를 가져오는 예제입니다.

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import HomePage from "./Page/HomePage"; // 폴더 구조 맞는지 확인
import RootLayout from "./components/RootLayout";
import EventPage from "./Page/EventPage";
import EventDetailPage from "./Page/EventDetailPage";
import NewEventPage from "./Page/NewEventPage";
import EventLayout from "./components/EventLayout";
import EditEventPage from "./Page/EditEventPage";
function App() {
  const router = createBrowserRouter([
    {
      path: "/",
      element: <RootLayout />,
      children: [
        { index: true, element: <HomePage /> },
        {
          path: "events",
          element: <EventLayout />,
          children: [
            { index: true, element: <EventPage />, loader: () => {} },
            { path: "new", element: <NewEventPage /> },
            { path: ":id", element: <EventDetailPage /> },
            { path: ":id/edit", element: <EditEventPage /> },
          ],
        },
      ],
    },
  ]);

  return <RouterProvider router={router} />;
}

export default App;
import { useEffect, useState } from "react";

import EventsList from "../components/EventsList";

function EventPage() {
  const [isLoading, setIsLoading] = useState(false);
  const [fetchedEvents, setFetchedEvents] = useState();
  const [error, setError] = useState();

  useEffect(() => {
    async function fetchEvents() {
      setIsLoading(true);
      const response = await fetch("http://localhost:8080/events");

      if (!response.ok) {
        setError("Fetching events failed.");
      } else {
        const resData = await response.json();
        setFetchedEvents(resData.events);
      }
      setIsLoading(false);
    }

    fetchEvents();
  }, []);

  return (
    <>
      <div style={{ textAlign: "center" }}>
        {isLoading && <p>Loading...</p>}
        {error && <p>{error}</p>}
      </div>
      {!isLoading && fetchedEvents && <EventsList events={fetchedEvents} />}
    </>
  );
}

export default EventPage;
import classes from "./EventsList.module.css";

function EventsList({ events }) {
  return (
    <div className={classes.events}>
      <h1>All Events</h1>
      <ul className={classes.list}>
        {events.map((event) => (
          <li key={event.id} className={classes.item}>
            <a href='...'>
              <img src={event.image} alt={event.title} />
              <div className={classes.content}>
                <h2>{event.title}</h2>
                <time>{event.date}</time>
              </div>
            </a>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default EventsList;

 

코드를 보시면 event 페이지에서 이벤트 목록을 패칭하고 이벤트 리스트 컴포넌트에 리스트들을 props로 넘겨주고 있습니다

 

이 방식은 문제가 있는 건 아니지만, 컴포넌트가 렌더링된 후에 데이터를 가져오기 때문에 "깜빡임"이 생길 수 있고, 로딩/에러 상태 관리도 직접 해줘야 하는 번거로움이 있습니다.

 

 

개선된 방식: useLoaderData와 loader를 이용한 데이터 관리

React Router v6.4 이상에서는 라우트 정의에서 데이터를 로딩하는 loader 함수를 미리 설정해줄 수 있으며, 컴포넌트에서는 useLoaderData() 훅을 통해 해당 데이터를 바로 받아올 수 있습니다.

 

 

1. loader 함수 정의

export const eventDataLoader = async () => {
  const response = await fetch("http://localhost:8080/events");

  if (!response.ok) {
    //...
  } else {
    const resData = await response.json();
    return resData.events;
  }
};

 

loader는 반드시 프로미스를 리턴해야 하며, throw를 통해 오류 처리를 하면 React Router가 에러 페이지를 자동으로 처리해줄 수 있습니다.

 

React Router에서 loader 함수는 해당 라우트가 활성화될 때마다 자동으로 실행됩니다. 즉, 사용자가 어떤 페이지로 "이동하기 직전"에 실행되며, 데이터를 미리 가져온 뒤 컴포넌트가 렌더링됩니다.

 

loader 함수는 구체적으로 언제 실행되나요?

  1. 초기 페이지 진입 시
    브라우저 주소창에 URL을 직접 입력하거나, 앱이 처음 렌더링될 때 해당 라우트가 활성화되면 loader가 호출됩니다.
  2. 링크 클릭 등으로 라우트 이동 시
    <Link> 또는 navigate()를 통해 해당 라우트로 이동하면, 페이지 전환 전에 loader가 먼저 실행됩니다.
  3. 경로 파라미터(id 등)가 변경될 때
    예를 들어 /events/1에서 /events/2로 이동하면 같은 컴포넌트라도 라우트가 변경되므로, loader가 다시 실행됩니다.

💡 React Router는 브라우저의 주소 변경만 감지하는 것이 아니라 라우트 단위로 각각의 loader를 개별적으로 실행합니다.

실행되지 않는 경우는?

  • 같은 라우트에 머물러 있고, 파라미터나 쿼리가 변경되지 않았다면 loader는 다시 실행되지 않습니다.
  • 컴포넌트만 리렌더링되고 라우트는 바뀌지 않은 경우 (예: 내부 상태 변경)는 loader와 무관합니다.

 

2. 라우터 설정에 loader 추가

라우터를 정의하는 부분에 위에서 만든 loader를 연결합니다.

 

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import HomePage from "./Page/HomePage"; // 폴더 구조 맞는지 확인
import RootLayout from "./components/RootLayout";
import EventPage, { eventDataLoader } from "./Page/EventPage";
import EventDetailPage from "./Page/EventDetailPage";
import NewEventPage from "./Page/NewEventPage";
import EventLayout from "./components/EventLayout";
import EditEventPage from "./Page/EditEventPage";
function App() {
  const router = createBrowserRouter([
    {
      path: "/",
      element: <RootLayout />,
      children: [
        { index: true, element: <HomePage /> },
        {
          path: "events",
          element: <EventLayout />,
          children: [
            {
              index: true,
              element: <EventPage />,
              loader: eventDataLoader,
            },
            { path: "new", element: <NewEventPage /> },
            { path: ":id", element: <EventDetailPage /> },
            { path: ":id/edit", element: <EditEventPage /> },
          ],
        },
      ],
    },
  ]);

  return <RouterProvider router={router} />;
}

export default App;

 

그 후 라우터로가서 사용할 라우터의 loader 속성에 아까 생성했던 함수를 넣어줍니다 

 

해당 페이지로 이동시 페이지가 렌더링되기전에 loader의 속성으로 지정해준 함수를 통해 데이터가 패칭됩니다 

 

해당 함수에서 프로미스값을 리턴해주기만 하면 알아서 resolve된 데이터를 가져옵니다

 

 

3. useLoaderData로 데이터 가져오기

이제 EventPage에서는 별도의 useEffect 없이도 데이터를 바로 받아와 사용할 수 있습니다.

import EventsList from "../components/EventsList";
import { useLoaderData } from "react-router-dom";
function EventPage() {
  const events = useLoaderData();
  return (
    <>
      <EventsList events={events} />
    </>
  );
}

export default EventPage;

export const eventDataLoader = async () => {
  const response = await fetch("http://localhost:8080/events");

  if (!response.ok) {
    //...
  } else {
    const resData = await response.json();
    return resData.events;
  }
};

 

 

주의할점으로는 loader는 해당 라우트에서만 데이터를 가져오기 때문에 상위 라우트에서는 하위 라우트의 loader 데이터를 사용할 수 없습니다.