본문 바로가기

React

[React] router loader, useNavigation

 

loader() 사용

 

loader() 사용 전

import { useEffect, useState } from 'react';

import EventsList from '../components/EventsList';

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();
  }, []);
  return (
    <>
      <div style={{ textAlign: 'center' }}>
        {isLoading && <p>Loading...</p>}
        {error && <p>{error}</p>}
      </div>
      {!isLoading && fetchedEvents && <EventsList events={fetchedEvents} />}
    </>
  );
}

export default EventsPage;

컴포넌트 렌더링 전에 실행하는 함수가 없어 컴포넌트 내에 들어갈 데이터를 컴포넌트 내에 작성

 

loader() 사용 후

App.jsx

import { RouterProvider, createBrowserRouter } from 'react-router-dom';

import EditEventPage from './pages/EditEvent';
import ErrorPage from './pages/Error';
import EventDetailPage, {
  loader as eventDetailLoader,
  action as deleteEventAction,
} from './pages/EventDetail';
import EventsPage, { loader as eventsLoader } from './pages/Events';
import EventsRootLayout from './pages/EventsRoot';
import HomePage from './pages/Home';
import NewEventPage from './pages/NewEvent';
import RootLayout from './pages/Root';
import { action as manipulateEventAction } from './components/EventForm';
import NewsletterPage, { action as newsletterAction } from './pages/Newsletter';

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      {
        path: 'events',
        element: <EventsRootLayout />,
        children: [
          {
            index: true,
            element: <EventsPage />,
            loader: eventsLoader,
          },
          {
            path: ':eventId',
            // id를 등록하면 child 라우터에서도 loader데이터 사용 가능
            // 컴포넌트에서 const data = useRouteLoaderData('event-detail')로 데이터 가져올 수 있음
            id: 'event-detail',
            // EventDetailPage, EditEventPage에서 둘다 loader에서 return 되는
            // 데이터를 사용할 수 있음
            loader: eventDetailLoader, 
           
            children: [
              {
                index: true,
                element: <EventDetailPage />,
                action: deleteEventAction,
              },
              {
                path: 'edit',
                element: <EditEventPage />,
                action: manipulateEventAction,
              },
            ],
          },
          {
            path: 'new',
            element: <NewEventPage />,
            action: manipulateEventAction,
          },
        ],
      },
      {
        path: 'newsletter',
        element: <NewsletterPage />,
        action: newsletterAction,
      },
    ],
  },
]);

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

export default App;

Events.jsx

import { useEffect, useState } from 'react';
import {useLoaderData} from 'react-router-dom';

import EventsList from '../components/EventsList';

function EventsPage() {

  
  const data = useLoaderData();
  if(data.isError){
  	return <p>{data.message}</p>
  }
  
  const events = data.events;

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

export default EventsPage;

export function async loader(){
	 // 실제로 Response로 리졸빙되는 Promise 리턴
	 const response = await fetch('http://localhost:8080/events');

      if (!response.ok) {
       	return {isError: true, message: 'Error'}
      } else {
        return response;
      }

}

 

- loader : 함수를 값으로 취하는 프로퍼티

- 리액트 라우터는 항상 컴포넌트 렌더링 직전(페이지로 이동하기 전)에 loader 함수를 방문

- async 함수를 사용하는 경우 리액트 라우터는 자동으로 Promise 가 return 된건지 확인하고 Promise로 부터 리졸빙 된 데이터를 받아옴 > 개발자 입장에서는 Promise가 리턴되었는지를 신경 쓸 필요 없음 

- 리액트 라우터는 loader가 작업을 완료할때 까지 대기하고 완료 되면 페이지 렌더링

- loader 는 서버에서 실행되지 않고 브라우저에서 실행됨

- loacer 내에서는 리액트 훅을 사용할 수 없음. 컴포넌트가 아니기 떄문

리액트 라우터 버전 6 이상

 

error 처리 방법

1. 객체로 보내기

import { useEffect, useState } from 'react';
import {useLoaderData} from 'react-router-dom';

import EventsList from '../components/EventsList';

function EventsPage() {

  
  const data = useLoaderData();
  
  if(data.isError){
  	return <p>{data.message}</p>
  }
  
  const events = data.events;

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

export default EventsPage;

export function async loader(){
	 // 실제로 Response로 리졸빙되는 Promise 리턴
	 const response = await fetch('http://localhost:8080/events');

      if (!response.ok) {
       	return {isError: true, message: 'Error'}
      } else {
        return response;
      }

}

 

 

2. throw Error 던지기 

import { useEffect, useState } from 'react';
import {useLoaderData} from 'react-router-dom';

import EventsList from '../components/EventsList';

function EventsPage() {

  const data = useLoaderData();

  const events = data.events;

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

export default EventsPage;

export function async loader(){
	 // 실제로 Response로 리졸빙되는 Promise 리턴
	 const response = await fetch('http://localhost:8080/events');

      if (!response.ok) {
       // 방법 1
       //throw new Response(JSON.stringify({message: 'Could not fetch events'}),{status: 500})
      	// 방법 2
        return json({message: 'Could not fetch events'},{status: 500})
      } else {
        return response;
      }

}

- 404 Error 될 시, 라우터에 입력한 ErrorElement 화면에 표시

- 객체를 생성해서 Error Response를 던짐

- json()은 react-router-dom에서 import 할 수 있음 , json 형식의 데이터가 포함된 Response객체를 생성하는 함수, 대신 값을 Parsing 해줌

 

 

 

ErrorPage.jsx

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

export default function ErrorPage(){

    const error = useRouteError();
    
    let title = 'An error occured'
    let message = 'Something went wrong!';
    
    if(error.status === 500){
       // 방법 1
    	message = JSON.parse(error.data).message;
    	// 방법 2(json 사용 시 router가 대신 파싱해줌)
        message = 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>
    )

}

loader에서 throw 한 Error 객체를 라우터에 등록한 에러페이지에서 받을 수 있음

 

param 사용

import { useLoaderData, useParams } from "react-router-dom";
import EventItem from '../components/EventItem';

function EventDetailPage(){
    const data = useLoaderData();
    return <EventItem event={data.event} />
}

export default EventDetailPage;

export async function loader({request, params}){

    const id = params.eventId; //router 에 등록한 param 이름
    
    const response = await fetch('http://localhost:8080/events/' + id);
    if(!response.ok){
        return json({message: 'Could not fetch'},{status:500})
    }
    
    return response;



}

- 리액트 파라미터에 접근 가능 : loader() 함수를 호출하는 리액트 라우터가 그걸 실행할 때 실제로 객체 하나를 loader 함수에 전달함 request(요청 객체 url등의 정보) params(파라미터)

 

 

useNavigation 

라우터 전환상태 확인 가능

import MainNavigation from "../components/MainNavigation";
import {Outlet, useNavigation} from 'react-router-dom'
function RootLayout(){

    const navigation = useNavigation();

    

    return <>
        <MainNavigation />
        <main>
            {navigation.state === 'loading'&&<p>Loading...</p>}

        </main>
    </>

}

export default RootLayout;

전환상태가 로딩 중이면 Loading... 띄우기

주의 : 이미 화면에 표시되어있는 페이지 혹은 컴포넌트에 뜨게 됨

'React' 카테고리의 다른 글

[React] 인증  (1) 2024.05.02
[React] Router , action, defer()  (0) 2024.04.30
[React] router  (0) 2024.04.23
[React] Redux  (0) 2024.04.19
[React] Form Validation check  (0) 2024.04.18