지난 시간 복습
useEffect 훅
을 대신한Loader
기능을 통해서 데이터를 가져올 수 있게 됐다.
하지만 데이터 보내는 것도, 새 포스트를 작성하는 것은 리팩토링이 아직이다. 그러니까 데이터 가져오기 말고 데이터를 보내는 작업도 리팩토링이 필요하다.action
기능을 이용할 것임.
<목표 >
기존에 구현되어 있던 방식 :
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' 속성을 추가하고 액션을 지정하면 된다.
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",
},
});
}
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>
);