본문 바로가기

클라이언트/React
[리액트(React)] Router - loader

// loader()

- 서버에서 데이터를 가져온 후 컴포넌트를 렌더링할 수 있게 도와주는 react-router의 기능

- react-router 버전 6 이상을 사용해야 한다.


1. 데이터를 가져오는 페이지(컴포넌트)의 route definition에 loader property를 추가한다.

{ path: 'url', element: <컴포넌트 />, loader: () => {
  // 데이터 가져오기
  return data;
}},


- loader는 함수를 값으로 가진다.

- loader의 return 값은 element 컴포넌트와 해당 컴포넌트를 필요로 하는 모든 컴포넌트들(childern)에 전달된다.


* loader 함수 처리가 너무 복잡해지기 때문에, element 컴포넌트에 loader 함수를 넣을 수도 있다.

export async function loader함수명() {
  // data 가져오기
  return data;
}

2. 원하는 컴포넌트에서 loader 에서 return한 data를 사용한다.

const data명 = useLoaderData();

 

import 컴포넌트명, { loader as loader명 } from '경로';

{
  index: true,
  element: <컴포넌트명 />,
  loader: loader명,
},

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import RootLayout from './pages/Root';
import Homepage from './pages/Home';
import EventsPage, { loader as eventsLoader } from './pages/Events';
import EventDetailPage from './pages/EventDetail';
import EditEventPage from './pages/EditEvent';
import NewEventPage from './pages/NewEvent';
import EventsRootLayout from './pages/EventsRoot';

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    children: [
      { index: true, element: <Homepage /> },
      {
        path: 'events',
        element: <EventsRootLayout />,
        children: [
          {
            index: true,
            element: <EventsPage />,
            loader: eventsLoader,
          },
          { path: ':eventId', element: <EventDetailPage /> },
          { path: 'new', element: <NewEventPage /> },
          { path: ':eventId/edit', element: <EditEventPage /> },
        ],
      },
    ],
  },
]);

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

export default App;

 

import React from 'react';
import EventsList from '../components/EventsList';
import { useLoaderData } from 'react-router-dom';
import { getEvents } from '../plugins/eventAxios';

function EventsPage() {
  const events = useLoaderData();

  return <>{<EventsList events={events} />}</>;
}

export default EventsPage;

export async function loader() {
  const data = await getEvents();
  return data.result;
}

// useNavigation

- loader에서 데이터를 가져오는 중인지 확인할 수 있게 해준다.

const navitgation = useNavigation();

const isLoading = navitgation.state === 'loading';

// useRouteError

- loader에서 데이터를 가져올 때 발생하는 error를 받아오는 hook

1. 오류 throw

throw new Response(JSON.stringify({ message: '메시지' }), {
  status: status코드,
});

//json() 사용
json(
      { message: 'Could not fetch Events' },
      {
        status: 500,
      },
    );


- json() 함수가 Response 객체를 생성해준다.


2. 오류 정보 받기

const error = useRouteError();

//Response 객체 return한 경우
const message = JSON.parse(error.data).message; 

// json 함수 사용한 경우
const message = error.data.message;

import React from 'react';
import EventsList from '../components/EventsList';
import { useLoaderData } from 'react-router-dom';
import { getEvents } from '../plugins/eventAxios';

function EventsPage() {
  const events = useLoaderData();

  return <>{<EventsList events={events} />}</>;
}

export default EventsPage;

export async function loader() {
  const data = await getEvents();

  if (data.status === 'FAIL') {
    throw new Response(JSON.stringify({ message: 'Could not fetch Events' }), {
      status: 500,
    });
  } else if (data.status === 'SUCCESS') {
    return data.result;
  }
}

 

import React from 'react';
import PageContent from '../components/PageContent';
import { useRouteError } from 'react-router-dom';

function ErrorPage() {
  const error = useRouteError();

  let title = 'An error occurred!';
  let message = 'Something went wrong!';

  if (error.status === 500) {
    message = JSON.parse(error.data).message;
  }

  if (error.status === 404) {
    title = 'Not found';
    message = 'Could not find resource or page.';
  }

  return (
    <PageContent title={title}>
      <p>{message}</p>
    </PageContent>
  );
}

export default ErrorPage;

 

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import RootLayout from './pages/Root';
import Homepage from './pages/Home';
import EventsPage, { loader as eventsLoader } from './pages/Events';
import EventDetailPage from './pages/EventDetail';
import EditEventPage from './pages/EditEvent';
import NewEventPage from './pages/NewEvent';
import EventsRootLayout from './pages/EventsRoot';
import Error from './pages/Error';

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <Error />,
    children: [
      { index: true, element: <Homepage /> },
      {
        path: 'events',
        element: <EventsRootLayout />,
        children: [
          {
            index: true,
            element: <EventsPage />,
            loader: eventsLoader,
          },
          { path: ':eventId', element: <EventDetailPage /> },
          { path: 'new', element: <NewEventPage /> },
          { path: ':eventId/edit', element: <EditEventPage /> },
        ],
      },
    ],
  },
]);

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

export default App;

// 동적 route + loader

- 동적 route에서 loader 함수를 정의할 때, request와 params를 받을 수 있다.

- params를 통해 동적 segement에 접근할 수 있다.

export async function loader함수명({ request, params }) {
  const id = params.식별자명;
}

// useRouteLoaderData

- 자신의 route가 아닌 부모 route의 data를 받아올 때 사용한다.


1. route 정의

{
  path: ,
  loader: ,
  id: ,
  children: [
    {
      index: true,
      element: < />,
    },
  ],
},


- 부모의 loader에서 data를 받아오려면 id property를 추가해야 한다.(이름은 자유)


2. data 받기

const 변수명 = useRouteLoaderData('routeId');

import { getEventDetail } from '../plugins/eventAxios';
import { json, useRouteLoaderData } from 'react-router-dom';
import EventItem from '../components/EventItem';

function EventDetailPage() {
  const event = useRouteLoaderData('event-detail');

  return <>{<EventItem event={event} />}</>;
}

export default EventDetailPage;

export async function loader({ request, params }) {
  const id = params.eventId;
  const data = await getEventDetail(id);

  if (data.status === 'FAIL') {
    json(
      { message: 'Could not fetch Event detail' },
      {
        status: 500,
      },
    );
  } else if (data.status === 'SUCCESS') {
    return data.result.event;
  }
}

 

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import RootLayout from './pages/Root';
import Homepage from './pages/Home';
import EventsPage, { loader as eventsLoader } from './pages/Events';
import EventDetailPage, {
  loader as eventDetailLoader,
} from './pages/EventDetail';
import EditEventPage from './pages/EditEvent';
import NewEventPage from './pages/NewEvent';
import EventsRootLayout from './pages/EventsRoot';
import Error from './pages/Error';

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <Error />,
    children: [
      { index: true, element: <Homepage /> },
      {
        path: 'events',
        element: <EventsRootLayout />,
        children: [
          {
            index: true,
            element: <EventsPage />,
            loader: eventsLoader,
          },
          {
            path: ':eventId',
            loader: eventDetailLoader,
            id: 'event-detail',
            children: [
              {
                index: true,
                element: <EventDetailPage />,
              },
              { path: 'edit', element: <EditEventPage /> },
            ],
          },
          { path: 'new', element: <NewEventPage /> },
        ],
      },
    ],
  },
]);

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

export default App;

// defer 

- Promise를 연기한다.


1. loader 함수 정의

export async function loader() {
  return defer({
      키: Promise를반환하는함수()
    });
  }

2. defer된 데이터 사용


function 컴포넌트명() {
  const 변수명 = useLoaderData().키;

  return (
    <Suspense fallback={jsx코드}>
      <Await resolve={변수명}>
        {(promise완료data) => jsx코드}
      </Await>
    </Suspense>
  );
}


- defer함수에서 설정한 key로 data에 접근한다.

- data를 필요로 하는 컴포넌트나 jsx코드를 직접 렌더링하지 않고 Await 컴포넌트를 렌더링한다.

- Await 태그의 resolve에 응답을 기다리는 data를 설정한다.

- Await 태그 사이에 data를 받아온 후 실행할 함수를 넣어준다.

- Suspense 컴포넌트는 다른 data가 도착하길 기다리는 동안 fallback을 보여준다.

'클라이언트 > React' 카테고리의 다른 글

[리액트(React)] Router - useFetcher  (0) 2024.02.21
[리액트(React)] Router - action  (0) 2024.02.20
[리액트(React)] 동적 Route  (0) 2024.02.18
[리액트(React)] 라우터(Router)  (0) 2024.02.17
[리액트(React)] Redux Toolkit  (0) 2024.02.16