오류 메시지 : "todos"유형에 'InvalidateQueryFilters'유형과 공통적인 속성이 없습니다.
react-query 3.xx 버전 쓰는 다른 분과 비교해보니 그 분은 이런 Type에러가 안생기고 있었다...! 뭔가 이상하다
문제의 코드
// InputBox.tsx
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newTodo: Todo): Promise<void> => addTodo(newTodo),
onSuccess: () => {
// ⭐️⭐️ invalidateQueries안에 ""형식으로 queryKey 넣어줌
queryClient.invalidateQueries("todos");
},
});
// TodoItem.tsx
const queryClient = useQueryClient();
const { mutate: deleteTodoItem } = useMutation({
mutationFn: (id: string): Promise<void> => deleteTodo(id),
onSuccess: () => {
// ⭐️⭐️ 위와 마찬가지 1
queryClient.invalidateQueries("todos");
},
});
const { mutate: toggleTodoItem } = useMutation({
mutationFn: (todo: Todo): Promise<void> => toggleTodo(todo),
onSuccess: () => {
// ⭐️⭐️ 위와 마찬가지 2
queryClient.invalidateQueries("todos");
},
});
원인 : tanstack-query에서는 invalidateQueries에 queryKey를 넣는 문법이 달라졌던 것!!!
해결 : queryKey 넣어줄때 {queryKey: ["todos"]}
형태로 넣어주기!
// TodoLists.tsx
const {
data: todos,
isLoading,
isError,
// ⭐️ useQuery에서 queryKey 생성
}: TodosType = useQuery({
queryKey: ["todos"],
queryFn: getTodos,
});
// InputBox.tsx
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (newTodo: Todo): Promise<void> => addTodo(newTodo),
onSuccess: () => {
queryClient.invalidateQueries({
// ⭐️⭐️⭐️⭐️⭐️ tanstack-query에서 바뀐 문법!!
queryKey: ["todos"],
});
},
});
//
"todos"
로 작성했을때도 비록 type에러는 났어도 동작은 잘 했었다?!
-> tanstack: "그렇게 쓰는 형식은 이전 버전의 형식이어서, 동작하는 것 정도는 지원해주겠는데 type은 틀렸다고 에러를 보여줄거야."
확인 방법 1.
공식 문서에도 바뀌어있다.
https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation
확인 방법 2.
typescript는 vscode에서 ctrl(command) + 클릭 시 지원하는 타입을 확인할 수 있다.
InvalidateQueryFilters유형이 뭐지? -> invalidateQueries
ctrl + 클릭
invalidateQueries 안에 InvalidateQueryFilters
가 있구나! -> ctrl + 클릭
InvalidateQueryFilters는 QueryFilters로부터 나왔구나! -> QueryFilters
클릭
QueryFilters안에 queryKey라는 속성이 있고, 걔는 QueryKey를 받는구나 -> QueryKey
클릭
QueryKey에는 Array(배열)가 들어오는구나.
=> queryKey: [ ]
가 들어온다는걸 알 수 있다!
new Date
를 사용하는 경우 종종 type오류가 생긴다고 한다.// 문제의 코드
const sortedTodos = todos
? [...todos].sort((a, b) => {
if (sortOrder === "asc") {
return new Date(b.deadline) - new Date(a.deadline);
}
return new Date(a.deadline) - new Date(b.deadline);
})
: [];
// TodoLists.tsx
const sortedTodos = todos
? [...todos].sort((a, b) => {
// ⭐️ new Date를 변수로 선언
const deadlineA = new Date(a.deadline);
const deadlineB = new Date(b.deadline);
// ⭐️ 변수 앞에 + 붙여주기! -> 계산 가능한 숫자형식으로 인식함
if (sortOrder === "asc") {
return +deadlineB - +deadlineA;
}
return +deadlineA - +deadlineB;
})
: [];
getQueryData(["queryKey"])
[queryKey: QueryKey, data: TQueryFnData | undefined][]
이렇게 특이한 형식으로 나와서 배열 2개를 한번에 구조분해할당 해야됨...import { useParams } from "react-router-dom";
import { useQueryClient } from "@tanstack/react-query"; // queryClient를 가져옵니다.
const Detail = () => {
const queryClient = useQueryClient(); // queryClient를 가져옵니다.
const { id } = useParams<{ id: string }>(); // URL 파라미터로부터 id를 가져옵니다.
// queryClient에서 Todos 데이터를 가져옵니다.
// ⭐️⭐️⭐️ 캐싱된 데이터 가져오기!! getQueryData(queryKey: ["queryKey"])
// 배열 2개 구조분해할당 한 이유 : getQueriesData의 반환값이 queryKey배열, data배열이기때문
const [[queryKey, todos]] = queryClient.getQueriesData({
queryKey: ["todos"],
});
// 해당 ID에 해당하는 Todo를 찾습니다.
const todoItem = todosData?.find(todo => todo.id === id);
if (!todoItem) {
return <div>Todo가 없습니다.</div>;
}
return (
<>
<St.TodoList>
<St.TodoListBody>
<St.Span>{todoItem.title}</St.Span>
<p>{todoItem.content}</p>
<St.Time>
{new Date(todoItem.deadline).toLocaleDateString("ko-KR", {
year: "numeric",
month: "long", // "long"을 사용하면 월 이름이 됨
day: "numeric",
})}
까지
</St.Time>
</St.TodoListBody>
</St.TodoList>
</>
);
};
export default Detail;
// custom hook 만들기
export const hook이름 = () => {
const 실행로직 = blah blah
return { 실행로직 }
// 혹은 로직 실행 후의 값 등등 컴포넌트에서 가져다 쓸 값을 return으로 반환해주기
}
// 컴포넌트에서 사용하기
const { 실행로직 } = hook이름();
// '실행로직' 혹은 다른 return 반환값 중 사용할 데이터를 가져다 쓰면 끝!
// 🧡 기존 - useQuery로 todos 데이터 가져오기
import { useQuery } from "@tanstack/react-query";
import { getTodos } from "../api/todos-api";
import { Todo } from "../types/Todos";
interface TodosType {
data: Todo[] | undefined;
isLoading: boolean;
isError: boolean;
}
// .. 생략
const {
data: todos,
isLoading,
isError,
}: TodosType = useQuery({
queryKey: ["todos"],
queryFn: getTodos,
});
if (isLoading) {
return <div>Loading...</div>;
}
if (isError) {
return <div>Error</div>;
}
// ..생략
// 💚 custom hook 사용
// hook - useTodoQuery()
import { useQuery } from "@tanstack/react-query";
import { getTodos } from "../api/todos-api";
import { Todo } from "../types/Todos";
export interface TodosType {
data: Todo[] | undefined;
isLoading: boolean;
isError: boolean;
}
// useTodoQuery 생성 시작
export const useTodoQuery = () => {
const {
data: todos,
isLoading,
isError,
}: TodosType = useQuery({
queryKey: ["todos"],
queryFn: getTodos,
});
// 컴포넌트에서 가져다 쓸 값들 반환해주기
return { todos, isLoading, isError };
};
// 💚 컴포넌트에서 사용하기 (TodoList.tsx)
const { todos, isLoading, isError } = useTodoQuery();
// 🧡 기존 - 연달아 작성된 여러개의 useMutation
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { deleteTodo, toggleTodo, updateTodo } from "../api/todos-api";
// .. 생략
const queryClient = useQueryClient();
const { mutate: deleteTodoItem } = useMutation({
mutationFn: (id: string): Promise<void> => deleteTodo(id),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
const { mutate: toggleTodoItem } = useMutation({
mutationFn: (todo: Todo): Promise<void> => toggleTodo(todo),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
const { mutate: updateTodoItem } = useMutation({
mutationFn: ({
todo,
newTitle,
newContent,
}: {
todo: Todo;
newTitle: string;
newContent: string;
}): Promise<void> => updateTodo(todo, newTitle, newContent),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
// ..생략
const removeHandler = (id: string): void => {
deleteTodoItem(id);
};
const reLocateHandler = (todo: Todo): void => {
toggleTodoItem(todo);
};
const onEditHandler = (e: React.FormEvent<HTMLFormElement>): void => {
updateTodoItem({ todo, newTitle, newContent });
};
// 💚 custom hook 사용
// hook - useTodoMutation()
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { deleteTodo, toggleTodo, updateTodo } from "../api/todos-api";
import { Todo } from "../types/Todos";
interface UpdateTodoType {
todo: Todo;
newTitle: string;
newContent: string;
}
// useTodoMutation 생성 시작
export const useTodoMutation = () => {
const queryClient = useQueryClient();
const { mutate: deleteTodoItem } = useMutation({
mutationFn: (id: string): Promise<void> => deleteTodo(id),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
const { mutate: toggleTodoItem } = useMutation({
mutationFn: (todo: Todo): Promise<void> => toggleTodo(todo),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
const { mutate: updateTodoItem } = useMutation({
mutationFn: ({
todo,
newTitle,
newContent,
}: UpdateTodoType): Promise<void> => updateTodo(todo, newTitle, newContent),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["todos"],
});
},
});
// 컴포넌트에서 가져다 쓸 데이터 반환해주기
return { deleteTodoItem, toggleTodoItem, updateTodoItem };
};
// 💚 컴포넌트에서 사용하기 (Detail.tsx)
const { deleteTodoItem, toggleTodoItem, updateTodoItem } = useTodoMutation();
// ..생략
const removeHandler = (id: string): void => {
deleteTodoItem(id);
};
const reLocateHandler = (todo: Todo): void => {
toggleTodoItem(todo);
};
const onEditHandler = (e: React.FormEvent<HTMLFormElement>): void => {
updateTodoItem({ todo, newTitle, newContent });
};