본문 바로가기
개발/react-query

react-query v5에 useQuery의 onSuccess가 사라졌다.

by 안뇽! 2023. 12. 19.
반응형

사이드프로젝트하다가 api에 문제가 생겨서 손보던 중 react-query v5가 나온걸 보고 v5로 업데이트 했다.

인터페이스가 좀 바뀌어서 이것저것 고치는데 onSuccess를 넣을 자리가 안보였다.

검색해보니 react-query v5에서는 onSuccess가 사라졌다고 한다!

이 글에 나와있는데 주요한것만 작성해본다.

 

나쁜 Callback 제거

useQuery에서 onSuccess, onError, onSetteled가 제거되었다. (useMutation에는 잘 있다고 한다.)

 

개발자의 의도와 다르게 행동할 수 있기 때문이다.

 

onError

onError같은 경우는 root의 queryClient에서 사용하라고 한다.

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error) =>
      toast.error(`Something went wrong: ${error.message}`),
  }),
})

 

만약 api마다 error message를 커스텀하고 싶을 수 있다. 그땐 이렇게

const queryClient = new QueryClient({
  queryCache: new QueryCache({
    onError: (error, query) => {
      if (query.meta.errorMessage) {
        toast.error(query.meta.errorMessage)
      }
    },
  }),
})

export function useTodos() {
  return useQuery({
    queryKey: ['todos', 'list'],
    queryFn: fetchTodos,
    meta: {
      errorMessage: 'Failed to fetch todos',
    },
  })
}

 

onSuccess

아래와 같이 하지 말라고 한다.

export function useTodos() {
  const [todoCount, setTodoCount] = React.useState(0)
  const { data: todos } = useQuery({
    queryKey: ['todos', 'list'],
    queryFn: fetchTodos,
    //😭 제발 이러지 마세요
    onSuccess: (data) => {
      setTodoCount(data.length)
    },
  })

  return { todos, todoCount }
}

 

1. 추가 렌더링 생김

2. 캐시를 사용할 경우 콜백이 실행되지 않을 가능성이 있다.

 

2번은 한번도 생각해보지 못한 경우인데, staleTime만 정의해놓고 캐시에서 데이터를 읽어올때 onSuccess 는 문제를 일으킬 수 있다.

fetch가 발생해야 onSuccess가 실행되는데 캐시된 데이터를 사용하면 re-fetch되지 않으면서 onSuccess가 호출되지 않는다.

 

이러한 버그는 이유를 알지 못하면 추적하기 어렵다.

 

그럼 어떻게 해야할까?

대안 1. 꼭 필요할때 useEffect 쓰기.

이렇게 하면 cache를 사용하더라도 예상대로 동작한다(cache를 사용해도 console.log은 찍힌다 => useEffect의 dependency가 동작한다.)

export function useTodos(filters) {
  const { dispatch } = useDispatch()

  const query = useQuery({
    queryKey: ['todos', 'list', { filters }],
    queryFn: () => fetchTodos(filters),
    staleTime: 2 * 60 * 1000,
  })

  React.useEffect(() => {
    if (query.data) {
      dispatch(setTodos(query.data))
    }
  }, [query.data])

  return query
}

 

대안2. select 사용하기

select는 data가 존재할때만 호출된다. 그렇기때문에 data가 undefined인 경우를 고려할 필요가 없다.

export const useTodosQuery = () =>
  useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    select: (data) => data.map((todo) => todo.name.toUpperCase()),
  })

 

연산이 비쌀경우 다음과 같이 메모이제이션도 가능하다.

export const useTodosQuery = () =>
  useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
    // ✅ memoizes with useCallback
    select: React.useCallback(
      (data: Todos) => data.map((todo) => todo.name.toUpperCase()),
      []
    ),
  })

 

 

그 외

여러가지가 있겠지만 cacheTime의 이름이 부적절하다고 판단되어 gcTime으로 바뀌었다.

garbage collect Time인데 훨씬 직관적이다.

반응형