New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
useAsyncData does not pass errors from server to client-side #12885
useAsyncData does not pass errors from server to client-side #12885
Comments
It now shows https://stackblitz.com/edit/github-ygz9df-hnnre8?file=app.vue |
@nndnha If you need other details in the template you can assign them to a |
@danielroe That's why we got the I think the security risk here is out of scope, it's not related to Nuxt or Can we assign an |
@nndnha See nuxt/framework#2130 (comment) for the rationale. If you feel you need the full error details on client side hydration, perhaps you could share your use case? |
For |
@nndnha I understand. One note: if you need to get the status code on client side and don't mind an extra request, you can also rerun the fetch. const { error, refresh } = await useFetch('https://jsonplaceholder.typicode.com/404');
if (process.client && error.value) {
await refresh()
} |
@danielroe Thanks for that solution, but @pi0 does care about the performance right? An extra duplicate request is a performance penalty for both server(internal server instead of |
I think both are valid points here. While Standard usage would be: (note that error is used as a boolean -- <template>
<div v-if="data">{{ data }}</div>
<div v-else-if="error">
Error fetching data <button @click="refresh">retry</button>
</div>
<div v-else>Loading...</div>
</template>
<script setup>
const { pending, error, data, refresh } = await useFetch(
'https://jsonplaceholder.typicode.com/404'
);
</script> BTW if you really want to expose message, you can explicitly leak it with another state: <template>
<div v-if="data">{{ data }}</div>
<div v-else-if="error">Error fetching data: {{ fetchError }}</div>
<div v-else>Loading...</div>
</template>
<script setup>
const { data, error } = await useFetch('https://jsonplaceholder.typicode.com/404' );
const fetchError = useState('error', () => error.value.toString());
</script> @danielroe BTW we might do better error hydration on the client-side, using a new Error instead of changing the type to boolean or probably better always make it boolean as |
@pi0 I'm not using the error content for rendering, I want to use its content to make some checks on rendering. I want to detect the response status code whenever it's 400, 404, 401, 403, 5xx... We won't retry on 4xx errors without alteration to the request parameters, won't we? |
Fair point about retrying on specific errors. Thinking how we can improve |
@pi0 @danielroe In <template>
<div v-if="data">{{ data }}</div>
<div v-else-if="error === 404">
Page not found
</div>
<div v-else>
Error fetching data <button @click="refresh">retry</button>
</div>
</template>
<script setup>
const { pending, error, data, refresh } = await useFetch(
'https://jsonplaceholder.typicode.com/404',
{
transformError: err => err.response.status
}
);
</script> |
This would be a perfect way of solving the problem imo. I'm experiencing the same issue as @nndnha, in that I want the status code on the client side. For me it's to show appropriate error UI, so that my users know exactly what's gone wrong. My way of solving this until now was for some useFetchs (well, technically useLazyAsyncDatas, but who's counting) to only run on the client side, to ensure I have the full error context. In all cases, I was only ever looking at the status code anyway, and had a computed property to extract that from the rest of the context. I think my ideal situation would be for the error property to always just be the error code, but I can see that there are cases where the rest of the error context could be useful, so being able to write a custom transformation strikes the perfect balance - offering full flexibility and ease of use, without making any assumptions for you. I will also say that I appreciate Nuxt defaulting to hiding the error context because of the potential security issue it could cause - this is definitely the right approach to take for a default action (even if it does slightly hinder developer UX), and gives me great greater confidence that I'm not exposing myself to another issue somewhere else because I forgot to overwrite another default. Security first, additional functionality second. |
After a bit of search trough the ohmyfetch (the library used by nuxtjs mentioned in the documentation) I obtained the status code easily, this is my solution: <script setup lang="ts">
let errorStatus;
const { data: myData, error: error }: { data: any; error?: any } =
await useFetch(
"https://jsonplaceholder.typicode.com/404",
{
async onResponseError({ response }) { // onResponseError is a "ohmyfetch" method
errorStatus = response.status; // assign the error to the declared variable "errorStatus"
},
}
);
return { myData, error, errorStatus }; // returning all the variables
</script> This is my first response to a GitHub issue, hope i wrote it at least understandable |
I ran into the same issue trying to communicate statusCodes from data fetching performed on the server back to the client. I realized simply setting state on the server will lead to the desired result: <script setup>
// pinia
import { useAppStore } from '@/store/app'
// assuming the api endpoint returns a 404 error using the sendError helper from h3
const { data, refresh, error } = await useAsyncData('key', () => $fetch('/api/endpoint'))
if (process.server && error?.value) {
const nuxtApp = useNuxtApp()
const appStore = useAppStore()
// patch nuxt state management
useState('statusCode', () => fetchError.value.response.status)
// set the response status code if you like
nuxtApp.ssrContext.event.res.statusCode = fetchError.value.response.status
// patch pinia
appStore.statusCode = fetchError.value.response.status
}
console.log(appStore.statusCode) // 404 on both server and client
console.log(useState('statusCode').value) // 404 on both server and client
</script> |
I tweaked this solution a little so it can be reusable: // ~/composables/useAsyncDataWithError.ts
import type {
AsyncData,
KeyOfRes,
PickFrom,
_Transform,
} from 'nuxt/dist/app/composables/asyncData'
import type { AsyncDataOptions, NuxtApp } from '#app'
export default async function useAsyncDataWithError<
DataT,
DataE = Error,
Transform extends _Transform<DataT> = _Transform<DataT, DataT>,
PickKeys extends KeyOfRes<Transform> = KeyOfRes<Transform>,
>(
key: string,
handler: (ctx?: NuxtApp) => Promise<DataT>,
options: AsyncDataOptions<DataT, Transform, PickKeys> = {},
): Promise<AsyncData<PickFrom<ReturnType<Transform>, PickKeys>, DataE | null | true>> {
const serverError = useState<DataE | true | null>(`error-${key}`, () => null)
const { error, data, ...rest } = await useAsyncData(key, handler, options)
// Only set the value on server and if serverError is empty
if (process.server && error.value && !serverError.value)
serverError.value = error.value as DataE | true | null
// Clear error if data is available
if (data.value)
serverError.value = null
return {
...rest,
data,
error: serverError,
}
} Then somewhere in your app const { error } = useAsyncDataWithError('key', () => $fetch('https://jsonplaceholder.typicode.com/404'))
// error value is same on both server and client |
There is already some way to easily get the body when response code is other than 200? |
Another solution would be to change your backend server to return status 200 instead of 4xx for usage errors, that way you can read the error message/code from the payload and nothing private from the Nuxt server would be exposed. |
I found another solution that will not only pass errors but also cookies. Assuming your server URL starts with
Then add
|
No. Status codes are there for a reason. A serverside application returns a 404 for a reason: I cannot find what you are looking for. Give me the bloody error. I have no information with a boolean. I do not know if there is an internal server error, if a request is malformed, etc. Nuxt should not every decide for me that I am an absolute beginner thus that they need to hide useful error data. |
Well-written rest API returns various error codes |
And might include data in that response body. 404 is pretty self explanatory, but a 400 might include a message as to why your request is malformed. |
It would be very helpful for me to have access to the error details. A request can fail for several reasons. Maybe the user is trying to access something that they haven't been authorized to access, in which case we might inform the user of this (and possibly suggest that they request access from the owner). Or maybe the user made a typo and they're (accidentally) trying to access something that doesn't exist, in which case we might tell the user to check for typos. Or maybe the server is down, in which case we might provide the user with a "Try Again" button. |
If you use |
In my case, I used promises. const { pending, error, data } = await useFetch(url, {
onResponse({ response }) {
return new Promise((resolve, reject) => {
response.ok ? resolve() : reject({ code: response.status, data: response.data })
})
}
})
if (error) {
// error is the value of the reject argument.
} |
I still don't understand why the nuxt developers won't share the error content with us, but I really liked your approach to this problem. I decided to use your code and thread on stackoverflow (https://stackoverflow.com/questions/72041740/how-to-set-global-api-baseurl-used-in-usefetch-in-nuxt-3) and it finally turned out to be something like this: const onResponseError = async ({ response }: { response: FetchResponse<ApiError> }) => {
// Handle error
};
const testApi = async <T>(request: NitroFetchRequest, options: UseFetchOptions<T extends void ? unknown : T, (res: T extends void ? unknown : T) => T extends void ? unknown : T, KeyOfRes<(res: T extends void ? unknown : T) => T extends void ? unknown : T>>) => {
const statusCode = ref<number>(200);
const asyncData = await useFetch<T>(request, {
// Other common options
onResponseError: async (ctx) => {
statusCode.value = ctx.response.status;
await onResponseError(ctx);
},
...options,
});
return { ...asyncData, statusCode };
}; Theoretically, I could also overwrite the |
anyone getting how to solve the same issue with onRequestError ? |
Environment
Linux
v14.18.1
3.0.0-27294839.7e5e50b
yarn@1.22.15
Vite
Reproduction
https://stackblitz.com/edit/github-ygz9df?file=app.vue
Describe the bug
On server side the error content is rendered as expected while the client side says it's null.
Additional context
No response
Logs
No response
The text was updated successfully, but these errors were encountered: