T
TanStack3y ago
reduced-jade

Avoiding state-sync when awaiting response body on error

Our API returns specific error messages as JSON in the response body on errors. So the HTTP code is 401 and the body has something like { error: 'Invalid password' }. I'm trying to avoid the onError callback when writing my useQuery/useMutation code (since those callbacks are being deprecated) and this article explains the downsides to how this stuff leads to "state sync" but I can't find any way to avoid it because JSON body doesn't get parsed when there's an error, so I have to do it myself (I'm using ky as the http lib). If there's an error, it seems like I have to use await error.response.json() to get the actual JSON data and that has to be done inside a callback or a useEffect. Is there a better way?
const {
error,
isError,
isSuccess,
mutate: login,
status
} = useMutation({
mutationFn: ({ username, password }: Model) => {
const formData = new FormData()
formData.append('username', username)
formData.append('password', password)

return ky.post('api/login', { body: formData })
}
})

if (isSuccess) {
navigate(searchParameters.get('returnTo') ?? import.meta.env.BASE_URL, { replace: true })
}

useEffect(() => {
const getError = async (): Promise<void> => {
if (error instanceof HTTPError) {
const result = await (error.response as KyResponse).json<APIError>()
setErrorMessage(result.error)
} else if (error instanceof Error) {
setErrorMessage(error.message)
} else {
setErrorMessage('An unknown error occurred.')
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
getError()
}, [error, setErrorMessage])
const {
error,
isError,
isSuccess,
mutate: login,
status
} = useMutation({
mutationFn: ({ username, password }: Model) => {
const formData = new FormData()
formData.append('username', username)
formData.append('password', password)

return ky.post('api/login', { body: formData })
}
})

if (isSuccess) {
navigate(searchParameters.get('returnTo') ?? import.meta.env.BASE_URL, { replace: true })
}

useEffect(() => {
const getError = async (): Promise<void> => {
if (error instanceof HTTPError) {
const result = await (error.response as KyResponse).json<APIError>()
setErrorMessage(result.error)
} else if (error instanceof Error) {
setErrorMessage(error.message)
} else {
setErrorMessage('An unknown error occurred.')
}
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
getError()
}, [error, setErrorMessage])
3 Replies
ambitious-aqua
ambitious-aqua3y ago
The callbacks are fine for mutations, they should only be avoided for queries. For queries you can use the global callbacks like here: https://tkdodo.eu/blog/react-query-error-handling#the-global-callbacks
React Query Error Handling
After covering the sunshine cases of data fetching, it's time to look at situations where things don't go as planned and "Something went wrong..."
reduced-jade
reduced-jadeOP3y ago
why would they be ok for mutations but not queries
ambitious-aqua
ambitious-aqua3y ago
Dominik (@TkDodo)
@sonatard yes, they are very good for useMutation, because mutations are triggered imperatively, and each invocation is it's own "instance". It's totally different for queries.
Twitter

Did you find this page helpful?