본문 바로가기

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

- route에서 데이터를 가져오는 것이 loader라면, 보내는 것은 action이다.
* loader:  https://sorrel012.tistory.com/393 참고

 

[리액트(React)] Router - loader

// loader() - 서버에서 데이터를 가져온 후 컴포넌트를 렌더링할 수 있게 도와주는 react-router의 기능 - react-router 버전 6 이상을 사용해야 한다. 1. 데이터를 가져오는 페이지(컴포넌트)의 route definitio

sorrel012.tistory.com


 

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

{ path: 'url', element: <컴포넌트 />, action: () => {
  // 데이터 보내기
}},


- action은 함수를 값으로 가진다.


* action함수 처리가 너무 복잡해지기 때문에, 보통 element 컴포넌트에 action함수를 정의한다.

export async function action함수명({request, params}) {
  const result = await request.formData();
  const 저장할객체명 = {
    키(key): result.get('name명'),
  };
  
  //데이터 보내기
  
  return redirect('이동할url')
}


- router가 capture해서 forwarding된 request를 잡기 위해 request, params를 받는다.

- redirect() 로 전송 완료 후 페이지 이동을 설정할 수 있다.


2. action을 trigger 한다.

// Form 컴포넌트

<Form method="post">
  <input name="name설정" required />
  <button>Save</button>
</Form>


- name이 필수로 설정되어 있어야 한다.

- submit하면 server로 바로 전송되는 것이 아니라 우리가 정의한 action으로 전송된다.

- 현재 활성화된 route의 action을 trigger하지 않는다면 action property를 추가하여 다른 route로 보낼 수 있다.


// useSubmit hook

- 프로그래밍적으로 action을 trigger 할 수 있다.

const submit = useSubmit();

submit({제출할 data}, {method});


- 첫 번째 인자인 submit하려는 데이터는 자동으로 formData 객체로 감싸진다.(없으면 null)

- 두 번째 인자에는 form의 property와 같은 option 들을 설정할 수 있다.
   ~ method, action...

- 두 번째 인자의 option들은 action 함수에서 request.option명으로 받을 수 있다.

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

function EventItem({ event }) {
  const submit = useSubmit();

  function 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;



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

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;
  }
}

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

  await axios
    .request({
      method: request.method,
      url: `http://localhost:8080/events/${eventId}`,
    })
    .catch(() => {
      throw json({ message: 'Could not delete event.' }, { status: 500 });
    });

  return redirect('/events');
}

 

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, {
  action as deleteEventAction,
  loader as eventDetailLoader,
} from './pages/EventDetail';
import EditEventPage from './pages/EditEvent';
import NewEventPage, { action as eventAction } 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 />,
                action: deleteEventAction,
              },
              { path: 'edit', element: <EditEventPage /> },
            ],
          },
          { path: 'new', element: <NewEventPage />, action: eventAction },
        ],
      },
    ],
  },
]);

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

export default App;

// useNavigation

- action에서 데이터를 보내는 중인지 확인할 수 있게 해준다.

const navitgation = useNavigation();

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

// useActionData

- loader에서 return 값을 useLoaderData()로 받아서 처리하는 것과 마찬가지로, action에서 return 값을 받는다.

- 자주 사용하지는 않지만, 오류를 받아와야 할 때 사용한다.

const data = useActionData();