[React] 라우터 - 고급기능 2

p-q·2023년 4월 15일
1

json() 유틸리티 함수

코드를 줄이고 수동으로 josn 형식 파싱할 필요없음

export const loader = async () => {
  const response = await fetch("http://localhost:8080/events");
  if (!response.ok) {
    throw json({ message: "Could not fetch events." }, { status: response.status});
  } else {
    return response;
  }
};

위 코드는 json() 유틸리티 함수를 사용하여 코드를 간소화하는 예제입니다.

이전 예제에서는 Response 객체를 사용하여 오류 응답을 생성하였습니다. 그러나, Response 객체는 기본적으로 문자열 데이터를 반환하므로, 수동으로 JSON.parse() 함수를 사용하여 JSON 데이터를 파싱해야 합니다. 이것은 코드를 복잡하게 만들 수 있습니다.

이를 해결하기 위해, json() 유틸리티 함수를 사용하여 JSON 데이터를 반환하는 Response 객체를 쉽게 생성할 수 있습니다. 위 예제에서는, response 객체를 생성할 때 json() 함수를 사용하여 데이터와 상태 코드를 설정하고, 이를 던져 오류 응답을 생성합니다.

이렇게 json() 유틸리티 함수를 사용하면 코드를 간소화하고, JSON 데이터를 쉽게 처리할 수 있습니다.

동적 라우트와 loader()

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage/>,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: "events",
        element: <EventLayout />,
        children: [
          {
            index: true,
            element: <EventPage />,
            loader: eventsLoader,
          },
          { path: ":eventId", element: <EventDetailPage /> , loader: eventDetailLoader },
          { path: ":eventId/edit", element: <EditEventPage /> },
          { path: "new", element: <NewEventPage /> },
        ],
      },
    ],
  },
]);
import React from 'react';
import { useLoaderData, json } from 'react-router-dom';
import EventItem from '../components/EventItem';

const EventDetailPage = () => {
  const data = useLoaderData();

  return (
    <EventItem event={data.event}/>
  );
};

export default EventDetailPage;

export const loader = async ({request, params}) => {
  const id = params.eventId;

  const response = await fetch(`http://localhost:8080/events/${id}`)

  if (!response.ok) {
    throw json({message: 'Could not fetch details for selected event.'}, {status: response.status})
  } else {
    const data = await response.json();
    return data;
  }

}

loader() 함수는 라우트와 관련된 데이터를 가져오는 역할을 합니다. 예를 들어, EventDetailPage는 eventId라는 동적 라우트 매개 변수를 사용합니다. 이 매개 변수는 params 객체에 전달되어 loader() 함수에서 사용됩니다. loader() 함수는 이벤트 ID를 사용하여 API에서 데이터를 가져와 반환합니다.

또한, useLoaderData 훅을 사용하여 로더 함수에서 반환한 데이터를 가져옵니다. 이 데이터는 컴포넌트에서 렌더링되는 이벤트 정보와 함께 사용됩니다.

loader() 함수에서 params 매개 변수를 사용하여 동적 라우트 매개 변수를 가져올 수 있습니다. 이 방법은 URL 쿼리 매개 변수를 가져올 때도 사용됩니다. 이를 통해 컴포넌트에서 URL 쿼리 매개 변수를 사용할 수 있습니다.

useRouteLoaderData 훅 및 다른 라우트에서 데이터에 액세스하기

useRouteLoaderData

이 훅을 사용하면 다른 라우트에서 데이터에 액세스하는 것이 더 쉬워집니다. 이를 사용하면, 상위 라우터의 로더에서 반환된 데이터를 하위 라우터에서 가져올 수 있습니다. 이를 통해 코드를 재사용하고 로직을 중앙 집중화하여 유지 관리가 용이해집니다.

라우터 구성:

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: "events",
        element: <EventLayout />,
        children: [
          {
            index: true,
            element: <EventPage />,
            loader: eventsLoader,
          },
          {
            path: ":eventId",
            id: "event-detail",
            loader: eventDetailLoader,
            children: [
              {
                index: true,
                element: <EventDetailPage />,
              },
              { path: "edit", element: <EditEventPage /> },
            ],
          },
          { path: "new", element: <NewEventPage /> },
        ],
      },
    ],
  },
]);

이 구성에서, event-detail 아이디가 할당된 라우터의 로더에서 반환된 데이터는 EventDetailPage와 EditEventPage에서 모두 사용할 수 있습니다

EventDetailPage 컴포넌트

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

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

  return (
    <EventItem event={data.event}/>
  );
};

export default EventDetailPage;

export const loader = async ({request, params}) => {
  const id = params.eventId;

  const response = await fetch(`http://localhost:8080/events/${id}`)

  if (!response.ok) {
    throw json({message: 'Could not fetch details for selected event.'}, {status: response.status})
  } else {
    const data = await response.json();
    return data;
  }

}

이렇게 함으로써, useRouteLoaderData 훅을 사용하여 상위 라우터의 데이터에 액세스하고, 동일한 데이터를 사용하는 여러 라우터에서 코드 재사용성이 향상되며, 중복 코드를 줄일 수 있습니다. 이 방식은 특히 여러 라우터가 같은 API 요청을 사용하거나 동일한 데이터를 처리할 때 유용합니다.

데이터 제출

데이터 제출을 처리하기 위해 라우터 객체에 action 프로퍼티를 추가하고 페이지 컴포넌트에서 이를 사용할 수 있습니다.

라우터 구성에 action 프로퍼티 추가

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: "events",
        element: <EventLayout />,
        children: [
          {
            index: true,
            element: <EventPage />,
            loader: eventsLoader,
          },
          {
            path: ":eventId",
            id: "event-detail",
            loader: eventDetailLoader,
            children: [
              {
                index: true,
                element: <EventDetailPage />,
              },
              { path: "edit", element: <EditEventPage /> },
            ],
          },
          { path: "new", element: <NewEventPage />, action: newEventAction },
        ],
      },
    ],
  },
]);

NewEventPage 컴포넌트에서 action 함수 추가

import React from "react";
import EventForm from "../components/EventForm";
import { json, redirect } from "react-router-dom";

const NewEventPage = () => {
  const submitHandelr = (event) => {
    event.preventDefault();
  };

  return <EventForm />;
};

export default NewEventPage;

export const action = async ({ request, params }) => {
  const data = await request.formData();

  const eventData = {
    title: data.get("title"),
    description: data.get("description"),
    location: data.get("location"),
    date: data.get("date"),
    image: data.get("image"),
  };

  const response = await fetch("http://localhost:8080/events", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(eventData),
  });

  if (!response.ok) {
    throw json({ message: "Could not create event." }, { status: response.status });
  }

  return redirect("/events");

};

이제 페이지 컴포넌트에서 action 함수가 데이터를 처리합니다. 이를 위해 react-router-dom의 Form 컴포넌트를 사용합니다.

EventForm 컴포넌트에서 Form 엘리먼트 대체

import { Form, useNavigate } from 'react-router-dom';

import classes from './EventForm.module.css';

const EventForm = ({ method, event }) => {
  const navigate = useNavigate();
  function cancelHandler() {
    navigate('..');
  }

  return (
    <Form method="post" className={classes.form}>
      <p>
        <label htmlFor="title">Title</label>
        <input id="title" type="text" name="title" required defaultValue={event ? event.title : ''} />
      </p>
      <p>
        <label htmlFor="image">Image</label>
        <input id="image" type="url" name="image" required defaultValue={event ? event.image : ''}/>
      </p>
      <p>
        <label htmlFor="date">Date</label>
        <input id="date" type="date" name="date" required defaultValue={event ? event.date : ''} />
      </p>
      <p>
        <label htmlFor="description">Description</label>
        <textarea id="description" name="description" rows="5" required defaultValue={event ? event.description : ''} />
      </p>
      <div className={classes.actions}>
        <button type="button" onClick={cancelHandler}>
          Cancel
        </button>
        <button>Save</button>
      </div>
    </Form>
  );
}

export default EventForm;

이제 기존 <form> 엘리먼트가 react-router-dom에서 제공하는 <Form> 컴포넌트로 대체되었습니다. <Form> 컴포넌트는 요청 데이터를 액션에 전달하게 됩니다. 이를 통해 폼의 데이터가 포함된 액션을 사용할 수 있습니다.

이 방법을 사용하면 페이지 컴포넌트에서 데이터 제출을 처리하고 서버에 데이터를 전송하는 기능을 추가할 수 있습니다. 또한 코드의 재사용성을 높일 수 있으며 각 페이지 컴포넌트에서 데이터 제출을 처리하는 로직을 간소화할 수 있습니다.

프로그램적으로 데이터 제출하기

Eventdetail 컴포넌트에 action 추가합니다.

export const action = async ({ request, params }) => {
  const id = params.eventId;

  const response = await fetch(`http://localhost:8080/events/${id}`, {
    method: request.method,
  });

  if (!response.ok) {
    throw json({ message: "Could not delete event." }, { status: response.status });
  }

  return redirect("/events");
};

라우터 설정에 삭제 action을 추가합니다.

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: "events",
        element: <EventLayout />,
        children: [
          {
            index: true,
            element: <EventPage />,
            loader: eventsLoader,
          },
          {
            path: ":eventId",
            id: "event-detail",
            loader: eventDetailLoader,
            children: [
              {
                index: true,
                element: <EventDetailPage />,
                action: deleteEventAction
              },
              { path: "edit", element: <EditEventPage /> },
            ],
          },
          { path: "new", element: <NewEventPage />, action: newEventAction},
        ],
      },
    ],
  },
]);

이벤트 삭제 동작은 EventDetail 컴포넌트 내부에서 이뤄지므로, 이곳에 action을 연결합니다.

EventItem 컴포넌트에 useSubmit을 사용하여 action을 연결합니다.

import { Link, useSubmit } from 'react-router-dom';
import classes from './EventItem.module.css';


const EventItem = ({ event }) => {

  const submit = useSubmit();

  const startDeleteHandler = () => {
    const proceed = window.confirm('Are you sure?');

    if ( proceed) {
      submit(null, {method: 'DELETE'});
    }
  }

  return (
    <article className={classes.event}>
      <img src={event.image} alt={event.title} />
      <h1>{event.title}</h1>
      <time>{event.date}</time>
      <p>{event.description}</p>
      <menu className={classes.actions}>
        <Link to="edit">Edit</Link>
        <button onClick={startDeleteHandler}>Delete</button>
      </menu>
    </article>
  );
}

export default EventItem;

제출 상태로 UI 상태업데이트 하기

useNavigation hook은 React Router v6에서 제공되는 hook 중 하나로, 현재 경로와 상태 정보를 제공합니다. 이를 활용하여 폼의 제출 상태를 관리할 수 있습니다.

import { useNavigation } from 'react-router-dom';

const EventForm = () => {
  const navigation = useNavigation();
  const isSubmitting = navigation.state === 'submitting';

  //...
}

navigation.state를 사용하여 제출 상태를 확인할 수 있습니다. 위 코드에서는 isSubmitting 변수를 사용하여 현재 폼이 제출 중인지 여부를 확인하고 있습니다.

제출 중일 때 UI를 업데이트하여 사용자에게 제출 중임을 알리는 것이 좋습니다. 이를 위해 isSubmitting 변수를 사용하여 버튼 텍스트를 변경할 수 있습니다.

const EventForm = () => {
  //...

  return (
    <Form>
      {/* ... */}
      <button disabled={isSubmitting}>{isSubmitting ? '제출 중' : '저장'}</button>
    </Form>
  );
}

제출 중일 때 버튼의 텍스트를 '제출 중'으로 변경하고, 그렇지 않을 때는 '저장'으로 변경합니다.

제출 중에는 중복 제출을 방지하기 위해 취소 버튼을 비활성화하는 것이 좋습니다. 이를 위해 isSubmitting 변수를 사용하여 취소 버튼을 비활성화할 수 있습니다.

const EventForm = () => {
  const navigate = useNavigate();
  const navigation = useNavigation();
  const isSubmitting = navigation.state === 'submitting';

  function cancelHandler() {
    if (!isSubmitting) {
      navigate('..');
    }
  }

  return (
    <Form>
      {/* ... */}
      <button onClick={cancelHandler} disabled={isSubmitting}>
        {isSubmitting ? '제출 중' : '저장'}
      </button>

사용자 입력을 검증하고 오류 출력하기

클라이언트 측 인증만으로는 보안에 취약하므로, 서버 측에서도 입력 검증을 수행해야 합니다. 이를 위해 action 함수에서 폼 데이터를 받아와 서버에 전송한 후, 서버의 응답에 따라 적절한 오류 메시지를 출력하도록 합니다.

import React from "react";
import EventForm from "../components/EventForm";
import { json, redirect } from "react-router-dom";

const NewEventPage = () => {
  return <EventForm />;
};

export default NewEventPage;

export const action = async ({ request, params }) => {
  const data = await request.formData();

  const eventData = {
    title: data.get("title"),
    description: data.get("description"),
    location: data.get("location"),
    date: data.get("date"),
    image: data.get("image"),
  };

  const response = await fetch("http://localhost:8080/events", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(eventData),
  });

  if( response.status === 422 ) {
    return response;
  }

  if (!response.ok) {
    throw json({ message: "Could not create event." }, { status: response.status });
  }

  return redirect("/events");

};

action 함수에서는 폼 데이터를 받아와 eventData 객체에 저장합니다. 그 다음, fetch 함수를 사용하여 서버에 데이터를 전송합니다. 서버의 응답에 따라 response 객체를 처리합니다.

response.status가 422인 경우에는 서버에서 전달된 오류 메시지를 출력합니다. 그렇지 않은 경우에는 /events 페이지로 리디렉션합니다.

javascript

import { Form, useActionData, useNavigate, useNavigation } from 'react-router-dom';

import classes from './EventForm.module.css';

const EventForm = ({ method, event }) => {
  const data = useActionData();

  const navigate = useNavigate();

  const navigation = useNavigation();

  const isSubmitting = navigation.state === 'submitting';

  function cancelHandler() {
    navigate('..');
  }

  return (
    <Form method="post" className={classes.form}>
      {data && data.errors && <ul>
          {Object.keys(data.errors).map((err) => <li key={err}>{err}</li>)}
        </ul>}
      <p>
        <label htmlFor="title">Title</label>
        <input id="title" type="text" name="title" required defaultValue={event ? event.title : ''} />
      </p>
      <p>
        <label htmlFor="image">Image</label>
        <input id="image" type="url" name="image" required defaultValue={event ? event.image : ''}/>
      </p>
      <p>
        <label htmlFor="date">Date</label>
        <input id="date" type="date" name="date" required defaultValue={event ? event.date : ''} />
      </p>
      <p>
        <label htmlFor="description">Description</label>
        <textarea id="description" name="description" rows="5" required defaultValue={event ? event.description : ''} />
      </p>
      <div className={classes.actions}>
        <button type="button" onClick={cancelHandler} disabled={isSubmitting}>
          Cancel
        </button>
        <button disabled={isSubmitting}>{isSubmitting ? 'Submmitng' : 'Save'}</button>
      </div>
    </Form>
  );
}

export default EventForm;

useActionData hook을 사용하여 data 변수에 서버에서 전달된 데이터를 가져옵니다. data 객체에는 errors 속성이 있으며, 이를 사용하여 서버에서 전달된 오류 메시지를 출력할 수 있습니다.

위 코드에서는 data 객체가 존재하고 errors 속성이 존재하는 경우에만 오류 메시지를 출력하도록 합니다.

request() 메서드로 액션 재사용하기

기존 NewEventPage 컴포넌트에서 action 함수를 EventForm 컴포넌트로 이동시킵니다. 이렇게 하면 NewEventPage와 EditEventPage에서 모두 EventForm을 사용할 수 있습니다.

action 함수를 수정하여 다양한 요청을 받을 수 있도록 합니다. 이를 위해 request.method를 사용하여 HTTP 메서드를 가져오고, PATCH 메서드가 사용되는 경우 URL을 수정합니다

export const action = async ({ request, params }) => {
  const method = request.method;
  const data = await request.formData();

  const eventData = {
    title: data.get("title"),
    description: data.get("description"),
    location: data.get("location"),
    date: data.get("date"),
    image: data.get("image"),
  };

  let url = "http://localhost:8080/events";

  if (method === "PATCH") {
    url += `/${params.eventId}`;
  }

  const response = await fetch(url, {
    method,
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(eventData),
  });

  if( response.status === 422 ) {
    return response;
  }

  if (!response.ok) {
    throw json({ message: "Could not create event." }, { status: response.status });
  }

  return redirect("/events");

};

라우트 객체에서 action 함수를 EventForm 컴포넌트에 지정해줍니다.

import { action as manipulateEventAction } from "./components/EventForm";

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: "events",
        element: <EventLayout />,
        children: [
          {
            index: true,
            element: <EventPage />,
            loader: eventsLoader,
          },
          {
            path: ":eventId",
            id: "event-detail",
            loader: eventDetailLoader,
            children: [
              {
                index: true,
                element: <EventDetailPage />,
                action: deleteEventAction
              },
              { path: "edit", element: <EditEventPage />, action: manipulateEventAction },
            ],
          },
          { path: "new", element: <NewEventPage />, action: manipulateEventAction},
        ],
      },
    ],
  },
]);

이렇게 하면 NewEventPage와 EditEventPage에서 모두 EventForm을 사용할 수 있으며, action 함수도 재사용할 수 있습니다.

useFetcher() 를 이용한 배후작업

useFetcher() 훅을 사용하여 페이지 전환이 필요하지 않은 상태에서도 데이터를 로드하고 처리할 수 있습니다. useFetcher() 훅은 useNavigate() 훅과 함께 사용됩니다. useNavigate() 훅은 페이지 전환이 필요하지 않은 상태에서도 액션을 트리거할 수 있습니다.

NewsletterSignup 컴포넌트에서 useFetcher()를 사용하여 데이터를 가져옵니다. fetcher.data 프로퍼티를 사용하여 데이터에 접근할 수 있습니다. fetcher.state 프로퍼티는 현재 데이터 상태를 나타냅니다.

import { useFetcher } from 'react-router-dom';

import classes from './NewsletterSignup.module.css';
import { useEffect } from 'react';

const NewsletterSignup = () => {

  const fetcher = useFetcher();
  const { data, state } = fetcher;

  useEffect(() => {
    if ( state === 'idle' && data && data.message) {
      alert(data.message);
    }
  }, [data, state])


  return (
    <fetcher.Form method="post" action='/newsletter' className={classes.newsletter}>
      <input
        type="email"
        name='email'
        placeholder="Sign up for newsletter..."
        aria-label="Sign up for newsletter"
      />
      <button>Sign up</button>
    </fetcher.Form>
  );
}

export default NewsletterSignup;

fetcher.Form을 사용하여 action 속성을 지정하면 라우트 컴포넌트를 로딩하지 않고도 액션을 실행할 수 있습니다. 이를 이용하면 공통된 컴포넌트나 같은 페이지에서 여러 번 사용되는 컴포넌트에서 데이터를 업데이트하거나 받을 수 있습니다.

이렇게 하면 NewsletterSignup 컴포넌트에서 페이지 전환이 필요하지 않은 상황에서도 데이터를 로드하고 처리할 수 있습니다.

defer() 함수로 데이터 가져오기 연기하기

React Router의 defer() 함수는 로딩되기 전까지 페이지를 연기시키는 방법을 제공합니다. 이 함수를 사용하면 데이터가 완전히 로딩되기 전에 페이지를 렌더링하려는 시도를 방지할 수 있습니다. 이를 통해 사용자는 페이지가 완전히 로드되기 전에 불완전한 데이터를 보는 것을 방지할 수 있습니다.

1. 데이터 가져오기 연기하기

데이터를 가져오는 것은 대개 시간이 걸립니다. 이것은 사용자가 웹 페이지를 로드할 때 모든 데이터가 즉시 사용 가능하지 않을 수 있다는 것을 의미합니다. 이 문제를 해결하기 위해 defer() 함수가 만들어졌습니다. defer() 함수는 데이터가 로드될 때까지 페이지를 연기시키고, 데이터가 로드되면 페이지를 렌더링합니다.

2. React Router의 defer() 함수

React Router의 defer() 함수는 컴포넌트에서 비동기 데이터 로딩을 연기시키는 데 사용됩니다. 이 함수는 promise를 반환합니다. 이 promise는 비동기 데이터 로딩이 완료될 때까지 기다립니다. 그런 다음, 데이터가 로드되면 반환됩니다.

3. 코드 예시

다음은 React Router의 defer() 함수를 사용하여 데이터를 로딩하는 예시 코드입니다.

import { useLoaderData, json, defer, Await } from "react-router-dom";

import EventsList from "../components/EventsList";
import { Suspense } from "react";

const EventsPage = () => {
  const { events } = useLoaderData();

  return (
    <Suspense fallback={<p style={{ testAlign: "center" }}>Loading...</p>}>
      <Await resolve={events}>{(events) => <EventsList events={events} />}</Await>
    </Suspense>
  );
};

export default EventsPage;

const loadEvents = async () => {
  const response = await fetch("http://localhost:8080/events");
  if (!response.ok) {
    throw json({ message: "Could not fetch events." }, { status: response.status });
  } else {
    const resData = await response.json();
    return resData.events;
  }
};

export const loader = () => {
  return defer({
    events: loadEvents(),
  });
};

이 코드는 useLoaderData() hook을 사용하여 defer() 함수에서 반환된 데이터를 가져옵니다. 이 코드에서는 useLoaderData() hook을 사용하여 데이터를 가져오기 전까지 페이지를 로딩합니다. 그러나 이 예시에서는 Suspense 컴포넌트가 로딩되는 동안 다른 데이터를 볼 수 있도록 설정됩니다.

4. Suspense 컴포넌트

React Router의 Suspense 컴포넌트는 다른 데이터가 로딩되는 동안에 폴백을 보여주는 특정한 상황에서 사용할 수 있습니다. 이 컴포넌트

연기할 데이터 제어하기

React 라우터를 사용할 때, 데이터를 로딩하는 것은 매우 중요한 부분입니다. 하지만 데이터를 로딩하는 것이 느리게 되면 사용자 경험이 나빠질 수 있습니다. 이를 방지하기 위해 defer를 사용하여 데이터 로딩을 제어할 수 있습니다.

1. React 라우터와 데이터 로딩

React 라우터는 매우 강력한 기능을 제공합니다. 페이지 전환을 할 때마다 해당 페이지의 데이터를 로딩하는 것은 매우 중요합니다. 그러나 데이터가 로딩될 때까지 사용자는 아무것도 볼 수 없기 때문에, 로딩을 빠르게 하여 사용자 경험을 개선할 필요가 있습니다.

2. defer를 사용한 데이터 로딩

React 라우터에서 defer를 사용하면 데이터 로딩을 제어할 수 있습니다. defer는 데이터가 로딩될 때까지 페이지 전환이나 렌더링을 연기하는 역할을 합니다.

3. defer 사용법

React 라우터에서 defer를 사용하려면, 라우터에 async loader를 추가하면 됩니다. 이 loader에서 데이터를 로딩하고 defer를 사용하여 페이지 전환을 연기합니다. 예를 들어, 아래의 코드는 이벤트 세부 정보 페이지에서 데이터를 로딩하고, defer를 사용하여 이벤트 세부 정보가 로딩될 때까지 페이지를 표시하지 않습니다.

import React, { Suspense } from "react";
import { useRouteLoaderData, json, redirect, defer, Await } from "react-router-dom";
import EventItem from "../components/EventItem";
import EventsList from "../components/EventsList";

const EventDetailPage = () => {
  const { event, events } = useRouteLoaderData("event-detail");

  return (
    <>
      <Suspense fallback={<p style={{ testAlign: "center" }}>Loading...</p>}>
        <Await resolve={event}>{(event) => <EventItem event={event} />}</Await>
      </Suspense>
      <Suspense fallback={<p style={{ testAlign: "center" }}>Loading...</p>}>
        <Await resolve={events}>{(events) => <EventsList events={events} />}</Await>
      </Suspense>
    </>
  );
};

export default EventDetailPage;

const loadEvent = async (id) => {
  const response = await fetch(`http://localhost:8080/events/${id}`);

  if (!response.ok) {
    throw json({ message: "Could not fetch details for selected event." }, { status: response.status });
  } else {
    const resData = await response.json();
    return resData.event;
  }
};

const loadEvents = async () => {
  const response = await fetch("http://localhost:8080/events");
  if (!response.ok) {
    throw json({ message: "Could not fetch events." }, { status: response.status });
  } else {
    const resData = await response.json();
    return resData.events;
  }
};

export const loader = async ({ request, params }) => {
  const id = params.eventId;

  return defer({
    event: await loadEvent(id),
    events: loadEvents(),
  });
};

export const action = async ({ request, params }) => {
  const id = params.eventId;

  const response = await fetch(`http://localhost:8080/events/${id}`, {
    method: request.method,
  });

  if (!response.ok) {
    throw json({ message: "Could not delete event." }, { status: response.status });
  }

  return redirect("/events");
};

이렇게 설정하면, 이벤트 세부 정보가 로딩될 때까지 페이지 전환이나 렌더링을 연기하여 사용자가 Loading... 텍스트를 볼 필요가 없게 됩니다. 이는 사용자 경험을 향상시키는 데 큰 도움이 됩니다. defer를 사용하면 원하는 데이터를 로딩할 때까지 페이지를 보여주지 않을 수 있기 때문에, 데이터 로딩을 제어하는 데 유용합니다.

profile
ppppqqqq

0개의 댓글