지금까지의 모든 mutations는 navigate form을 통해서 이루어 졌다.
이러한 user flow는 일반적이지만, navigation없이 데이터를 변경하는 것도 일반적이다.
이 경우, useFetcher
훅을 이용한다.
이 훅은 navigation없이 loaders 및 actions 와 통신할 수 있도록 해준다.
contact 페이지의 ⭐버튼이 이 경우에 적합하다.
새 레코드를 작성하거나 삭제하는 것도 아니고 페이지를 변경하는 것도 아니다.
현재 페이지의 데이터를 변경하는 것 뿐이다.
<Favorite>
" form to a fetcher form// 📄src/routes/contact.jsx
import {
useLoaderData,
Form,
useFetcher,
} from "react-router-dom";
// existing code
function Favorite({ contact }) {
const fetcher = useFetcher();
let favorite = contact.favorite;
return (
<fetcher.Form method="post">
<button
name="favorite"
value={favorite ? "false" : "true"}
aria-label={
favorite
? "Remove from favorites"
: "Add to favorites"
}
>
{favorite ? "★" : "☆"}
</button>
</fetcher.Form>
);
}
이 폼은 값이 "true" 및 "false" 중 하나인 favorite 키를 사용하여 formData를 전송한다.
method="post"
으로 설정했기 때문에 action
을 호출한다.
<fetcher.Form action="...">
prop이 없어서 랜더링한 route에 post 요청을 한다.
// 📄src/routes/contact.jsx
// existing code
import { getContact, updateContact } from "../contacts";
export async function action({ request, params }) {
let formData = await request.formData();
return updateContact(params.contactId, {
favorite: formData.get("favorite") === "true",
});
}
export default function Contact() {
// existing code
}
// 📄src/main.jsx
// existing code
import Contact, {
loader as contactLoader,
action as contactAction,
} from "./routes/contact";
const router = createBrowserRouter([
{
path: "/",
element: <Root />,
errorElement: <ErrorPage />,
loader: rootLoader,
action: rootAction,
children: [
{ index: true, element: <Index /> },
{
path: "contacts/:contactId",
element: <Contact />,
loader: contactLoader,
action: contactAction,
},
/* existing code */
],
},
]);
사용자 이름 옆에 별 버튼을 클릭할 준비가 완료되었다.
<fetcher.Form method="post">
는 <Form>
과 동일한 동작을 한다.
만약 오류가 발생하더라도 동일하게 동작할 것이다.
다만 한 가지 차이점은, <fetcher.Form method="post">
는 navigation이 아니란 점이다.
따라서 URL이 바뀌거나 히스토리 스택에 영향을 주지 않는다.
출처 : 리액트 라우터 공식 홈페이지➡️