함수를 값으로 취하는 프로퍼티 이 페이지를 라우팅하기전에 이 함수를 실행해줌
->loader안에 데이터를 로딩하고 가져올 수 있음
이전 fetch코드 Event.js
function EventsPage() {
  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();
  }, []);
App.js 에서 라우팅할때 loader 이용
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 /> },
        ],
      },
    ],
  },
]);
resData.events를 리턴하는이유 : 응답 데이터 객체가 실제 이벤트 어레이를 담고잇는 이벤트 프로퍼티를 갖게 되니까Event.js
useloaderData로 events에 데이터를 삽입
function EventsPage() {
  const events = useLoaderData();
  return <EventsList events={events} />;
}
export default EventsPage;
export async function loader() {
  const response = await fetch("http://localhost:8080/events");
  if (!response.ok) {
    // ...
  } else {
    const resData = await response.json();
    return resData.events;
  }
}
❖ 로딩하려는 위치보다 더 높은 위치에서 데이터를 로딩하면 안됨
 loader를 사용하면 페이지의 초기 렌더링이 완료된 후에 데이터를 가져오는 것이 아니라, 페이지가 로드되는 동안에 데이터를 가져온다. 
 따라서 UI적으로 로딩을 보여주는게 좋음 
usenavigationstate에loading을 넣어줘 쉽게 처리할 수 있다.
import { Outlet, useNavigation } from "react-router-dom";
import MainNavigation from "../components/MainNavigation";
function RootLayout() {
  const navigation = useNavigation();
  return (
    <>
      <MainNavigation />
      <main>
        {navigation.state === "loading" && <p>,,,,loading</p>}
        <Outlet />
      </main>
    </>
  );
}
export default RootLayout;
❖ usestate같은 리액트 훅은 불가능
function ErrorPage() {
  const error = useRouteError();
  let title = "An error occurred!";
  let message = "Something went wrong!";
  if (error.status === 500) {
    message = error.data.message;
  }
  if (error.status === 404) {
    title = "Not found!";
    message = "Could not find resource or page.";
  }
  return (
    <>
      <MainNavigation />
      <PageContent title={title}>
        <p>{message}</p>
      </PageContent>
    </>
  );
}
export default ErrorPage;
Event.js
  if (!response.ok) {
    // return { isError: true, message: 'Could not fetch events.' };
    throw new Response(JSON.stringify({ message: "Could not fetch events." }), {
      status: 500,
    });
  } else {
    return response;
  }
}
.data:  HTTP 응답 오류에서는 오류와 관련된 추가적인 정보를 담고 있는 객체
.message: 오류 객체의 메시지를 나타내는 속성입
error.data.message는 오류 객체의 데이터에서 메시지를 가져오는 것