Form & Action( )

hyeryeon·2024년 8월 26일

React

목록 보기
14/19

react-router-dom 패키지는 동적 라우팅과 함께 데이터를 처리하는 Form 컴포넌트와 action 함수를 제공합니다.
이를 활용해 사용자 입력을 효율적으로 처리하고 서버로 전송할 수 있습니다.

1. action() 사용해서 post요청하기

EventForm 컴포넌트 구현

먼저, 사용자에게 이벤트 데이터를 입력받을 EventForm 컴포넌트를 구현합니다.
이 컴포넌트는 Form 컴포넌트를 사용하여 폼 데이터를 리액트 라우터의 action 함수로 직접 전달합니다.

import { useNavigate, Form } from 'react-router-dom';
import classes from './EventForm.module.css';

function EventForm({ 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;

그러기 위해선 폼 양식 컴포넌트에서 모든 input요소에 name속성이 있어야 한다. 그래야 나중에 데이터 추출할때 그것을 보고 할 수 있다.

그리고 나서 react-router-dom이 제공하는 Form이라는 컴포넌트로 일반 html태그와 교체 해주어야 한다.

  • 이 Form컴포넌트는 벡엔드로 요청을 전송하는 브라우저 기본사항을 생략하고 전송되는 그 요청을 받아서 내가 지정하는 action함수에 준다.
    - 그럼 제출된 모든 데이터가 포함되어 있기에 사용할 수 있다.

그렇기 때문에 우선 Form컴포넌트 안에 method프로퍼티를 추가해준다.
⭐ 여기서 중요한 것은 자동으로 백엔드에 바로 전송되는것이 아닌 action으로 전송되는 것!!

action 함수 구현

먼저 라우터를 정의한 곳에 가서 NewEventPage가 등록된 라우터에 action 프로퍼티를 추가한다.

이 action은 함수를 받으며, 해당 로직은 그 로직이 속해야할 컴포넌트에 가까이 두는 편이 좋다.

import { json, redirect } from 'react-router-dom';

export async function action({ request }) {
  const data = await request.formData();// Form으로 받은 입력값들에 접근하기 위한 방법이다. 
  const eventData = {
    title: data.get('title'),
    image: data.get('image'),
    date: data.get('date'),
    description: data.get('description'),
  };

  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 save event.' }, { status: 500 });
  }

  return redirect('/events');
}

이를 사용하려면 formData메소드를 통해 접근해야한다. 그리고나서 이도 비동기로 받아온것이기에 await을 걸어주고 get메소드를 통해 제출된 다양한 입력 필드에 접근할 수 있게 되는것이다.

get에 아까 input의 name속성으로 지정한 식별자들을 넣어주면 해당하는 값이 추출된다.

이렇게 리엑트 라우터가 해당 action함수에 전달한 요청데이터를 추출할 수 있게 되었다.즉, 리엑트 라우터가 백엔드로 최종적으로 보내기 전, 프론트단에서 충분히 데이터 처리를 거치고 보내주는것이다.

이제는 이 데이터를 백엔드로 보내주어야한다.'

app.js

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

import EditEventPage from './pages/EditEvent';
import ErrorPage from './pages/Error';
import EventDetailPage, {
  loader as eventDetailLoader,
} from './pages/EventDetail';
import EventsPage, { loader as eventsLoader } from './pages/Events';
import EventsRootLayout from './pages/EventRoot';
import HomePage from './pages/Home';
import NewEventPage, { action as newEventAction } from './pages/NewEvent';
import RootLayout from './pages/Root';

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: 'event-detail',
            loader: eventDetailLoader,
            children: [
              {
                index: true,
                element: <EventDetailPage />,
              },
              { path: 'edit', element: <EditEventPage /> },
            ],
          },
          { path: 'new', element: <NewEventPage />, action: newEventAction },
        ],
      },
    ],
  },
]);

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

export default App;

Action() 다시보기

action 함수는 리액트 라우터의 기능 중 하나로, 사용자의 폼 제출과 같은 특정 액션을 처리하고 그 결과를 서버에 전달하며, 그 응답에 따라 적절한 라우트 변경이나 데이터 업데이트를 수행합니다.

파라미터: request, params

  • request
    이 객체는 HTTP 요청과 관련된 데이터를 포함하고 있습니다.
    폼 데이터를 처리하기 위해 사용됩니다.
  • params: URL 경로에서 파라미터를 추출할 때 사용되는 객체입니다.

함수의 실행 과정

  1. 폼 데이터 추출
    request.formData()를 호출하여 폼에서 제출된 데이터를 비동기적으로 가져옵니다. 이 메소드는 FormData 객체를 반환하며, 이 객체에서 get 메소드를 사용해 필요한 데이터를 추출합니다.
 const data = await request.formData();
  const eventData = {
    title: data.get('title'),
    image: data.get('image'),
    date: data.get('date'),
    description: data.get('description'),
  };
  1. 서버 요청 설정
    추출한 데이터를 JSON 포맷으로 변환하여 서버의 특정 엔드포인트(/events)로 POST 요청을 보냅니다.
    요청 헤더에는 Content-Type을 application/json으로 설정하여, 서버가 이를 JSON 형식으로 파싱할 수 있도록 합니다.
  const response = await fetch('http://localhost:8080/events', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(eventData),
  });
  1. 응답 처리
    fetch 함수로 서버로부터 응답을 받습니다.
    응답의 상태 코드가 성공적이지 않은 경우(response.ok가 false인 경우), 오류 메시지와 함께 예외를 발생시킵니다.
    이를 위해 json 함수를 사용하여, 오류 응답을 생성하고 이를 throw합니다.
 if (!response.ok) {
    throw json({ message: 'Could not save event.' }, { status: 500 });
  }

  return redirect('/events');
}
  1. 리디렉션
    모든 처리가 성공적으로 완료되면, redirect 함수를 호출하여 사용자를 이벤트 목록 페이지(/events)로 리디렉션합니다.

action() 사용해서 delete 요청하기

post요청을 해봤으니 이제 해당 데이터를 삭제하기 위한 헨들링도 해볼 필요가 있다.
비슷하게 action함수를 사용해야한다.

현재 상세페이지에서 해당 게시물을 삭제할 수 있는 버튼이 자리하고 있으므로, EventDetailPage에서 action을 생성해주고 생성한 action을 올바른 위치의 라우터에 등록해준다.

import { useRouteLoaderData, json, redirect } from 'react-router-dom';

import EventItem from '../components/EventItem';

function EventDetailPage() {
  const data = useRouteLoaderData('event-detail');
  {/* 백엔드에서 eventId 엔드포인트에 대한 응답줄때 객체 전체에서 event 프로퍼티에 응답 데이터를 포함시키고 있다. */}
  return <EventItem event={data.event} />;
}

export default EventDetailPage;

// 리엑트라우터가 백엔드로 바로 보내지않고 loader로 요청 객체를 전달해준다, 그걸 파라미터로 받아서 사용가능하다.
export async function loader({ 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: 500,
      }
    );
  } else {
    return response;
  }
}

export async function action({params,request}){
  const eventId = params.eventId;
  const response = await fetch('http://localhost:8080/events/'+eventId,{
    method: request.method,
  });
  if (!response.ok) {
    throw json(
      { message: 'Could not delete event.' },
      {
        status: 500,
      }
    );
  } 
  return redirect('/events');

}

이렇게 action을 설정해 줬으면, 해당 이벤트가 트리거되어야하는(삭제 버튼이 있는) EventItem컴포넌트에서 설정해주어야 한다.

이때, 삭제할 헨들링에서는 사용자에게 한번의 피드백을 주기위한 조건을 작성하고 그 값이 true일때 그때가 되서야 삭제할 event가 정상적으로 제출되도록 해야한다.

그때 리엑트 라우터에서 제공하여 사용할 수 있는 훅이 바로 useSubmit훅이다.

useSubmit으로 만들어진 함수에 첫번째 인자로 제출하려는 데이터를 넣어주어야 하고, 두번째 인자로는 폼에 설정할 수 있는 값과 같은 값들을 설정할 수 있게 해준다.

지금 이 헨들링은 삭제를 하기 위함이니 method를 delete로 설정해준다.
다만, 첫번째 인자엔 null을 해줘야 하는데 이는 현재 여기선 데이터가 필요하진 않기 때문이다.

이를 통해 submit은 백엔드에 전송하기 전 action에 요청객체를 전달해 주는것이다!

eventitem.js

import { Link, useSubmit } from 'react-router-dom';

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

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;

위처럼 useSubmit훅을 통해 보낸 데이터는 요청 객체가 될것이며 이를 실제 action함수에서 request객체로 사용가능하다.

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

  const response = await fetch(`http://localhost:8080/events/${id}`, {
    method: request.method,
    headers: {
      "Content-type": "application/json",
    },
  });

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

  return redirect("/events");
}

https://velog.io/@ksa199653/React-%EB%A6%AC%EC%97%91%ED%8A%B8-%EB%9D%BC%EC%9A%B0%ED%84%B0-loader-3

0개의 댓글