개발 일기

2024-11-29(tanstack query)

민ズl 2024. 11. 29. 17:26
const fetchData = async () => {
    try {
      const response = await todoApi.get("/todos?_sort=-createdAt");
      setData(response.data);
    } catch (err) {
      setError(err);
    } finally {
      setIsLoading(false);
    }
  };

try vs finally중에 뭐가 먼저 실행될까?
=> 둘다!

 

tanstack query

  • isPending
    • isLoading -> inPending 으로 명칭 변경
    • QueryClient에 아직 데이터가 없고 쿼리가 아직 실행 대기중인지 여부
    • 쿼리가 현재 실행중인지 여부 (초기로딩 및 refetching 을 모두 포함)
  • 얕은비교를 해서 재렌더링 방지
  • useQuery 에서 언제 상태변경요청(setState) 하는가?
    • → queryFn이 실행되어 값을 반환하는 즉시, 그 값이 QueryClient 의 서버상태로 설정됩니다.
  • isPending vs isFetching 
    • isPending은 QueryClient에 아직 데이터가 없고 쿼리가 아직 실행 대기중인지 여부
    • isFetching은 쿼리가 현재 실행중인지 여부 (초기로딩 및 refetching 을 모두 포함)
  • QueryFunctionContext
    • queryFn 의 매개변수로 전달되는 객체
const { data, isPending, error } = useQuery({
   queryKey: ["todos", id],
   queryFn: async (context) => {
      const { queryKey, meta, signal, pageParam } = context; // QueryFunctionContext
      const [,id] = queryKey;
      const response = await todoApi(`/todos/${id}`);
      return response.data;
    },
  });
  • QueryCache 를 통한 Global Callback 을 통한 에러처리 (toast같은 alert 라이브러리 사용가능)
    • 첫번째 매개변수 : error (에러)
    • 두번째 매개변수 : query (어디서온 에러인지)
    • query cahe란 밑에 사진

// main.jsx
const queryClient = new QueryClient({
   queryCache: new QueryCache({
      onError: (error, query) => {
         if(query.meta.source === "todos") {
            toast.error(`Something went wrong in TodoList: ${error.message}`),
         }
      }
   }),
});
  • enabled 옵션
    • 마치 수도꼭지같이 실행 조정
    • 기본값은 true
    • true인 경우에만 queryFn 실행
    • Disabling/Pausing Queries (이벤트 발생 시에만 수동 실행하고 싶을 때)
    • Dependent Queries (useQuery 2개 이상이며 실행순서 설정 필요할 때)
  • select 옵션
    • queryFn 에 의해 리턴된 값을 변형시킨 후에 useQuery 의 리턴 data로 넘겨줌.
    • (단, cache data 는 queryFn 에서 리턴받은 값 그대로임)
    • 데이터 변경은 queryFn에서!
import { useQuery } from 'tanstack/react-query'

function User() {
  const { data } = useQuery({
	  queryKey: ['user'], 
	  queryFn: fetchUser,
    select: (user) => user.username,
  })
  return <div>Username: {data}</div>
}
  • 낙관적 업데이트(Optimistic Update)
    • 서버 요청이 정상적으로 잘 될거란 가정하에 UI 변경을 먼저하고, 서버 요청 하는 방식
    • 혹시라도 서버 요청이 실패하는 경우, UI 를 원상복구(revert / roll back)
const queryClient = useQueryClient()

useMutation({
  mutationFn: updateTodo, // 실행 2
  // When mutate is called:
  onMutate: async (newTodo) => { // 실행 1
    // Cancel any outgoing refetches
    // (so they don't overwrite our optimistic update)
    await queryClient.cancelQueries({ queryKey: ['todos'] })

    // Snapshot the previous value
    const previousTodos = queryClient.getQueryData(['todos'])

    // Optimistically update to the new value
    queryClient.setQueryData(['todos'], (old) => [...old, newTodo])

    // Return a context object with the snapshotted value
    return { previousTodos }
  },
  // If the mutation fails,
  // use the context returned from onMutate to roll back
  onError: (err, newTodo, context) => {
    queryClient.setQueryData(['todos'], context.previousTodos)
  },
  
  onSettled: () => { //성공하든 실패하든 항상 실행
    queryClient.invalidateQueries({ queryKey: ['todos'] })
  },
})
  • useMutation 의 mutationFn 의 매개변수를 한 개만 받음
const { mutate: handleLike } = useMutation({
   // 💩 mutationFn의 매개변수는 한 개만 받을 수 있어요!
    mutationFn: (id, currentLiked) =>
      todoApi.patch(`/todos/${id}`, { liked: !currentLiked }),
    onSuccess: () => {
      queryClient.invalidateQueries(["todos"]);
    }
})

const { mutate: handleLike } = useMutation({
   // 🎉 객체 구조분해할당을 이용해서 여러개의 데이터를 매개변수로 받을 수 있어요!
    mutationFn: ({ id, currentLiked }) =>
      todoApi.patch(`/todos/${id}`, { liked: !currentLiked }),
    onSuccess: () => {
      queryClient.invalidateQueries(["todos"]);
    }
})