TC - 20번일지 (리팩토링 action)

Debug-Life ·2023년 3월 23일
0

지난 시간 복습

useEffect 훅 을 대신한 Loader 기능을 통해서 데이터를 가져올 수 있게 됐다.
하지만 데이터 보내는 것도, 새 포스트를 작성하는 것은 리팩토링이 아직이다. 그러니까 데이터 가져오기 말고 데이터를 보내는 작업도 리팩토링이 필요하다. action 기능을 이용할 것임.


<목표 >

1. submit 기능 처리

기존에 구현되어 있던 방식 :

NewPost.jsx 에서는 사용자 입력으로 submit을 받으면 이벤트를 자동으로 감지해서 들어온 이벤트 값으로 새 Post를 더해주고, 모달창을 꺼주는 방식.


수정 목표 :

백엔드로 데이터를 전송하는 코드를 리팩토링.
(기존 PostList에 구현된 fetch 함수로 처리 하는것이 아닌 라우터의 action 속성을 이용해서 구현)


✍ NewPost.jsx (수정 후)

import { useState } from "react";
import { Link } from "react-router-dom";

import Modal from "../components/Modal";
import classes from "./NewPost.module.css";

function NewPost(props) {
  const [enteredBody, setEnteredBody] = useState("");
  const [enteredAuthor, setEnteredAuthor] = useState("");

  function bodyChangeHandler(event) {
    setEnteredBody(event.target.value);
  }
  function authorChangeHandler(event) {
    setEnteredAuthor(event.target.value);
  }
  function submitHandler(event) {
    event.preventDefault();
    const postData = {
      body: enteredBody,
      author: enteredAuthor,
    };
    fetch("http://localhost:8080/posts", {
      method: "POST",
      body: JSON.stringify(postData),
      headers: {
        "Content-Type": "application/json",
      },
    });
  }

  return (
    <Modal>
      <form className={classes.form} onSubmit={submitHandler}>
        <p>
          <label htmlFor="body">Text</label>
          <textarea id="body" required rows={3} onChange={bodyChangeHandler} />
        </p>
        <p>
          <label htmlFor="name">Your name</label>
          <input
            type="text"
            id="name"
            required
            onChange={authorChangeHandler}
          />
        </p>
        <p className={classes.actions}>
          <Link to=".." type="button">
            Cancel
          </Link>
          <button>Sumbit</button>
        </p>
      </form>
    </Modal>
  );
}

export default NewPost;


그런데 이렇게 해놓으면 코드가 길어진다.
데이터를 보내고 나서 모달을 닫을 거라서, fetch 로 데이터를 가져온 이후에 라우터로 주소를 이동시켜줘야함. (메인페이지가 보이도록)
그러니까 그냥 복잡함. 그리고 의미가 없음. 왜냐하면 PostList에서 fetch 코드만 NewPost로 이동만 한 것이기 때문에.


그래서 action을 이용한다.
라우트에 로더를 추가해 데이터를 로드한 뒤에 라우트를 활성화하고 컴포넌트를 렌더링했던 것처럼 라우트에 액션을 추가할 수 있다.

loader 기능을 쓸 때와 마찬가지로 라우터 정의 부분에 'action' 속성을 추가하고 액션을 지정하면 된다.

2. action 특징

  • action은 데이터를 백엔드로 보내는 역할.
    기존 http 요청 fetch로 구현했었던 긴 코드를 줄여줌.

  • 등록해둔 라우트가 실행될 때 실행됨.

  • loader와 동일하게 main.jsx 의 유지보수가 간편하게 하기 위해서NewPost.jsx 에서 action() export 한다



✍ NewPost.jsx (export action 추가 후)

import { useState } from "react";
import { Link } from "react-router-dom";

import Modal from "../components/Modal";
import classes from "./NewPost.module.css";

function NewPost(props) {
  const [enteredBody, setEnteredBody] = useState("");
  const [enteredAuthor, setEnteredAuthor] = useState("");

  function bodyChangeHandler(event) {
    setEnteredBody(event.target.value);
  }
  function authorChangeHandler(event) {
    setEnteredAuthor(event.target.value);
  }
  function submitHandler(event) {
    event.preventDefault();
    const postData = {
      body: enteredBody,
      author: enteredAuthor,
    };
  }

  return (
    <Modal>
      <form className={classes.form} onSubmit={submitHandler}>
        <p>
          <label htmlFor="body">Text</label>
          <textarea id="body" required rows={3} onChange={bodyChangeHandler} />
        </p>
        <p>
          <label htmlFor="name">Your name</label>
          <input
            type="text"
            id="name"
            required
            onChange={authorChangeHandler}
          />
        </p>
        <p className={classes.actions}>
          <Link to=".." type="button">
            Cancel
          </Link>
          <button>Sumbit</button>
        </p>
      </form>
    </Modal>
  );
}

export default NewPost;

export function action() {
  fetch("http://localhost:8080/posts", {
    method: "POST",
    body: JSON.stringify(postData),
    headers: {
      "Content-Type": "application/json",
    },
  });
}

3. 추가한 부분

  • 상태 업데이트 부분 전부 삭제
  • 사용자가 입력한 데이터를 가져와 action에 추가.
  • 입력창에 name 속성 추가. ( textarea 요소에

Form 컴포넌트 사용

  • 기본 내장 'form' 대신 사용하면 라우터가 폼 전송을 처리해 브라우저의 요청을 막음. ( event.preventDefault 함수와 동일한 동작)
  • 모든 입력 데이터를 수집, 해당 데이터로 객체를 구성해줌.
  • Form 을 갖고 있으면 action()을 호출한다.

✍ NewPost.jsx (Form 컴포넌트, name 속성 추가 후)

import { Link, Form, redirect } from "react-router-dom";

import Modal from "../components/Modal";
import classes from "./NewPost.module.css";

function NewPost(props) {
  return (
    <Modal>
      <Form method="post" className={classes.form}>
        <p>
          <label htmlFor="body">Text</label>
          <textarea id="body" name="body" required rows={3} />
        </p>
        <p>
          <label htmlFor="name">Your name</label>
          <input type="text" name="author" id="name" required />
        </p>
        <p className={classes.actions}>
          <Link to=".." type="button">
            Cancel
          </Link>
          <button>Sumbit</button>
        </p>
      </Form>
    </Modal>
  );
}

export default NewPost;

export async function action({ request }) {
  const formData = await request.formData();
  const postData = Object.fromEntries(formData);
  await fetch("http://localhost:8080/posts", {
    method: "POST",
    body: JSON.stringify(postData),
    headers: {
      "Content-Type": "application/json",
    },
  });

  return redirect("/");
}



  • 아래 main.jsx 코드로 action 추가하고 나면,
  • NewPost.jsx 에서 라우터가 action 함수(newPostAction) 를 실행한다.
  • NewPost.jsx Form 요소중에 method 속성 추가.
  • 그리고 action 함수의 매개변수는 라우터가 자동으로 넘겨줌.
    폼에서 받은 데이터가 아니라, 객체형식임. 그래서 request 라는 프로퍼티로 접근해야 라우터가 만든 객체에 접근할 수 있음.
  • formData() 메서드 : Form에서 입력받은 데이터를 가져올 수 있음

✍ main.jsx (action 속성 추가 후)

import React from "react";
import ReactDOM from "react-dom/client";
import { RouterProvider, createBrowserRouter } from "react-router-dom";

import ErrorBoundary from "./components/ErrorBoundary";
import Posts, { loader as postsLoader } from "./routes/Posts";
import NewPost, { action as newPostAction } from "./routes/NewPost";
import RootLayout from "./routes/RootLayout";
import "./index.css";

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      {
        path: "/",
        element: (
          <ErrorBoundary>
            <Posts />
          </ErrorBoundary>
        ),
        loader: postsLoader,
        children: [
          { path: "/create-post", element: <NewPost />, action: newPostAction },
        ],
      },
    ],
  },
]);

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);



4. 동작 방식 정리

  • 라우터 정의 부분에 action 추가.
  • action은 값으로 함수받음. 그 함수는 해당 컴포넌트에서 export 하기.
  • form요소에서 Form 컴포넌트로 바꿔줌. 이것이 브라우저가 사용자가 보낸 데이터를 백엔드로 보내는것을 막아줌.
  • Form 컴포넌트가 action 함수 호출함.
  • name 속성 추가해 보낸 데이터의 이름을 붙여서 데이터가져올떄 찾게 해줌
  • 데이터를 보내고 나선,
  • redirect()를 호출하면 응답 객체가 만들어지고 action() 함수는 그 객체를 반환함. action함수로 데이터를 보내고나면 리액트 라우터가 메인페이지로 이동하게 함.
    그니까 제출버튼 누르면 모달이 꺼지고, 메인 화면으로 이동시켜준다는 뜻.



profile
인생도 디버깅이 될까요? 그럼요 제가 하고 있는걸요

0개의 댓글