nuxt: Error thrown `nuxt instance unavailable`, when use `useState` after `useFetch` in composable

Environment


  • Operating System: Darwin
  • Node Version: v16.13.0
  • Nuxt Version: 3.0.0-rc.3-27538180.cad4edd
  • Package Manager: npm@8.7.0
  • Builder: vite
  • User Config: -
  • Runtime Modules: -
  • Build Modules: -

Reproduction

https://stackblitz.com/edit/github-cibhp2-szhe5i?file=composables%2Ffetch-data.ts

export const useStateBeforeUseFetch = async () => {
  const { data } = await useFetch('/api/test');
  const state = useState('test');
};

Describe the bug

when I use useState after async useFetch , error was thrown nuxt instance unavailable

Additional context

No response

Logs

[nitro] [dev] [unhandledRejection] Error: nuxt instance unavailable
    at Module.useNuxtApp (file:///home/projects/github-cibhp2-szhe5i/.nuxt/dist/server/server.mjs:408:13)
    at Module.useState (file:///home/projects/github-cibhp2-szhe5i/.nuxt/dist/server/server.mjs:948:38)
    at Module.useStateBeforeUseFetch (file:///home/projects/github-cibhp2-szhe5i/.nuxt/dist/server/server.mjs:3089:39)

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 17 (5 by maintainers)

Most upvoted comments

I’m having this same problem and on top of @hermesalvesbr, I have to say the composable functionality is extremely frustrating with only a very slim chance of usage.

At this point I don’t even know the point behind composables anymore - the documentation says it’s to make things easier and avoid boilerplate - but instead we ended up with more complicated and fragile code. On top of this, it seems to me that we are slowly getting composable-only features.

This may sound a bit of a rant (and it is really), I’ve spent a few days trying to get some functionality to work and instead it’s jumping back and forth between component hooks and setup composables.

Anyway, I just want to say that (1) there is real frustration behind these “features” and (2) you can see this from end users. So maybe it’s still a good time to go one step back and rethink it a bit.

I agree with @uuf6429 understanding how to use the provided composables is extremely difficult. The limitation on async is understandable but I get why people are confused. Nuxt provides a composable for useFetch which by its very nature is async and therefore people will naturally try to extend that with their own async functionality. I think throwError is another gotcha; given that most people will want to throw an error after an HTTP request, throwError has the same limitations and extremely limited usability for actual applications.

Then there is useState, which is cumbersome to use if it cannot be used after an async event such as an HTTP request. I had better luck adopting pinia as a storage mechanism and using $fetch directly as necessary. I then use pinia for a global store for any errors my application may have as well.

Sorry I do not mean to sound as if I am piling on just wanted to share my learnings from trying to adopt Nuxt. I do understand that these concepts are extremely difficult to document accurately in a way that everyone will understand. Given there are many different concepts that share similar names. Middleware for SSR or middleware for the h3 api server? Does server mean the h3 api server or the server performing the SSR?

@uuf6429 Thank you for your thoughts.

Rest assured, making things better and more intuitive is top of my mind.

You may find the following helpful in terms of understanding how Vue composables work, and what their limitations are - particularly note the Usage Restrictions section: https://vuejs.org/guide/reusability/composables.html#composables.

composables should be run syncronously in most places. (We do some special magic within the body of middleware, and <script setup> that makes it possible to mix them in the body of the function, but we don’t transform your own composables.)

I also meet this error, in the server middleware the request occurs, everything is OK, and if the page hangs and after a couple of minutes I update page, the error is [nuxt] [request error] nuxt instance unavailable -> [Vue warn]: Unhandled error during execution of setup function

I haven’t found a solution to this, but I came up with a workaround that might be helpful. I have in my app many endpoints that are available globally, they’re the same for everyone, and rarely change, so I wanted them to be called only once in the server, cached, and parsed in the frontend, making 0 unnecessary calls.

I’ve tried to create a composable for this but eventually encountered the same issue found here, so I came up with the following workaround:

const [config, categoryThree, availableComponents, regions] = await Promise.all([
  useFetch('/app-config'),
  useFetch("/categories/three"),
  useFetch(`/editor/cached/available-components`),
  useFetch(`/regions`),
])

useState('available-components', () => availableComponents.data.value)
useState('categories-three', () => categoryThree.data.value)
useState('regions', () => regions.data.value)

I’m fetching them all on app.vue, and then serializing the results with useState(), then I can access this data everywhere in the app with export const useCategories = () => useState('categories-three') as Ref<Category[]>. I guess this is probably how this composable was supposed to be used in the first place.

@ilyaDegtyarenko Do you have some example code?

Yes, sure

pages/page.vue

<script
    setup
    lang="ts"
>
    const {data, pending, refresh} = await useNuxtApp().$api.operator.paginate(...)
...

plugins/apiServicePlugin.ts

...
export default defineNuxtPlugin(() => {
    return {
        provide: {
            api: {
                operator: OperatorService
            }
        }
    }
})

services/operator.service.ts

import {useAppFetch} from '#imports'

export default {
    paginate({...}: OperatorPaginate) {
        return useAppFetch(`/url`, {...})
    },
...
}

composables/useAppFetch.ts

...

export const useAppFetch = async (request: FetchRequest, options: UseFetchOptions<unknown> = {}) => {
    options.baseURL = useRuntimeConfig().apiUrl

    !options.headers && (options.headers = {})

    options.headers['Accept-Language'] = useLang().language.value

    const token = useCookie<string>('token')

    if (validate(token.value)) {
        setAuthHeader(options, token.value)
    } else {
        const {data, error} = await useNuxtApp().$api.auth.refreshToken(...)
        ...
    }

    options.onResponseError = async ({response}: FetchContext & { response: FetchResponse<ResponseType> }): Promise<void> => {
        ...
    }

    return useFetch(request, options) as Promise<_AsyncData<any, any>>
}