📝
[id] route
에 이미 todo의 isDone 상태를toggle
하는PATCH
api가 존재하는 상황에서, todo의 내용을 수정(update
)하는 로직의PATCH
도 만들고 싶었는데, 같은 route파일에서는 PATCH를 한번만 선언할 수 있다?!
이럴땐 어떻게 해야할까?
// ✅ Csr.tsx - todoList(next.js)
// ...생략
// ⭐️ todo의 isDone상태를 toggle하는 mutation 함수
const { mutate: toggleTodo } = useMutation({
mutationFn: async ({ id, isDone }: { id: string; isDone: boolean }) => {
await fetch(`http://localhost:3000/api/todos/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
// ⭐️ body로 isDone을 직접 보내줌
body: JSON.stringify({ isDone }),
});
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
// ⭐️ todo에 새로운 내용으로 update하는 함수
const { mutate: updateTodo } = useMutation({
mutationFn: async ({
id,
nextTitle,
nextContents,
}: {
id: string;
nextTitle: string;
nextContents: string;
}) => {
const response = await fetch(`http://localhost:3000/api/todos/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
// ⭐️ body에 새롭게 입력받은 내용을 전달함
body: JSON.stringify({ nextTitle, nextContents }),
});
},
});
// ...생략
// ✅ route.ts - app / api / todos / [id]
// ⭐️ db에 접근하여 isDone상태를 toggle하는 PATCH api
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
const { isDone } = await request.json();
const id = params.id;
const response = await fetch(`http://localhost:4005/todos/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
// ⭐️ isDone상태를 반대로 바꿔주는 내용을 body로 전달
body: JSON.stringify({ isDone: !isDone }),
});
return Response.json({
id,
isDone,
});
}
// ❌ update하는 PATCH로직을 아래에 새로 만들려고 시도하니 PATCH를 재선언 할 수 없다는 오류발생!
해결방법 1
// ✅ Csr.tsx - todoList(next.js)
// toggle - url 수정
const { mutate: toggleTodo } = useMutation({
mutationFn: async ({ id, isDone }: { id: string; isDone: boolean }) => {
// ⭐️ [id] 폴더 안에 toggle폴더 하나 더 만들어서 url 수정함
await fetch(`http://localhost:3000/api/todos/${id}/toggle`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ isDone }),
});
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
const { mutate: updateTodo } = useMutation({
mutationFn: async ({
id,
nextTitle,
nextContents,
}: {
id: string;
nextTitle: string;
nextContents: string;
}) => {
const response = await fetch(`http://localhost:3000/api/todos/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ nextTitle, nextContents }),
});
},
// 성공 후 id state도 빈값으로 돌려야 input에서 p태그로 바뀜
onSuccess: () => {
setSelectedId("");
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
// ✅ route.ts - app / api / todos / [id]
// ⭐️ [id]의 route에서는 updateTodo PATCH api
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
const id = params.id;
const { nextTitle, nextContents } = await request.json();
const response = await fetch(`http://localhost:4005/todos/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
// body에 전달하는 값으로 db가 수정된다.
body: JSON.stringify({ title: nextTitle, contents: nextContents }),
});
return Response.json({
id,
});
}
// ✅ route.ts - app / api / todos / [id] / toggle
// 기존과 동일, 위치만 [id] / toggle 폴더 안의 route로 옮김
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
const { isDone } = await request.json();
const id = params.id;
const response = await fetch(`http://localhost:4005/todos/${id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ isDone: !isDone }),
});
return Response.json({
id,
isDone,
});
}
해결방법 2
해결방법 2 보다는, 간단하고 효율적인 1번 방법을 사용하자.
현업에서는 1번 방법 사용 시 백엔드와 조율이 필요하다는 점만 기억하자!
문제상황
: custom hook으로 mutation함수들을 분리하고, 거기에 useState로 상태관리까지 넣어놨는데, 분명 똑같은 코드를 컴포넌트 분리한 것 뿐인데 분리하기 전엔 잘 되던 코드가 분리하고 나니까 오류는 안났지만 제대로 동작하지 않았다.
원인
: custom hook에서 관리하는 useState 상태관리는 만약 자식 컴포넌트까지 그 상태 변화가 전달되어야할때는 부적합한 사용이었다.
해결
: 전역상태관리를 통해 상태관리를 해야한다!
👍🏻 그렇다면 이 참에 Zustand를 써보자!
공식문서 : https://docs.pmnd.rs/zustand/getting-started/introduction
Redux에서는 provider로 store를 내려줘야했는데, zustand에서는 그냥 custom hook 가져오듯이 useTodoStore(예시) 에서 state와 리듀서 함수 가져오면 됨
가져올때는 useSelector 쓰는 것처럼 안에 콜백함수로 인자는 state, 결과값에는 state.초기값 혹은 state.함수 이렇게 가져와서 쓰면 된다! Redux 혹은 RTX보다 아주 간단하다
예시 코드
// ✅ useUpdateStore.ts
import { create } from "zustand";
export interface StateType {
selectedId: string;
nextTitle: string;
nextContents: string;
}
// store 생성하기(use로 시작해야함, 인자로 set이 들어옴)
export const useUpdateStore = create((set) => ({
// 초기값(이름은 임의로 지정가능)
updateState: {
selectedId: "",
nextTitle: "",
nextContents: "",
},
// 액션 생성자 함수
updateSelectedId: (id: string) =>
set((state: StateType) => ({ ...state, selectedId: id })),
updateTitle: (nextTitle: string) =>
set((state: StateType) => ({ ...state, nextTitle: nextTitle })),
updateContents: (nextContents: string) =>
set((state: StateType) => ({
...state,
nextContents: nextContents,
})),
}));
// ✅ 컴포넌트에서 사용하는 예시
// store에서 바로 state와 액션 생성자 함수를 가져올 수 있다.
const { selectedId, nextTitle, nextContents } = useUpdateStore(
(state: any): StateType => state.updateState
);
const updateSelectedId = useUpdateStore(
(state: any) => state.updateSelectedId
);
const updateTitle = useUpdateStore((state: any) => state.updateTitle);
const updateContents = useUpdateStore((state: any) => state.updateContents);