nuxt / nuxt

The Intuitive Vue Framework.
https://nuxt.com
MIT License
54.93k stars 5.03k forks source link

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

Closed ItsuoSano closed 2 years ago

ItsuoSano commented 2 years ago

Environment


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)
rikbrowning commented 2 years ago

I've hit this issue continuously trying to write server code. Seems that as soon as you perform an async/await the NuxtApp can no longer be found.

ilyaDegtyarenko commented 2 years ago

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

igorbelikov commented 2 years ago

+1

danielroe commented 2 years ago

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.)

ilyaDegtyarenko commented 2 years ago

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'm getting this error when refreshing a page after waiting for some idle time on a page where using useAppFetch I'm waiting for data in a <script setup

useAppFetch is my compossable where I wrapped useFetch and added options there, also catch errors and validate token

danielroe commented 2 years ago

@ilyaDegtyarenko Do you have some example code?

ilyaDegtyarenko commented 2 years ago

@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>>
}
danielroe commented 2 years ago

You should get the nuxt instance before any async operations. And I would suggest that you not use plugins in this context, but expose a shared utility so you don't need to call useNuxtApp at all to access it.

ilyaDegtyarenko commented 2 years ago

You should get the nuxt instance before any async operations. And I would suggest that you not use plugins in this context, but expose a shared utility so you don't need to call useNuxtApp at all to access it.

I turned the plugin into an utility file that I import into the page component, but I still get an error when refreshing the page after a while.

~/utils/api.ts

import OperatorService from '~/services/operator.service'

export default {
    operator: OperatorService
}

page component

import api from '~/utils/api'

const {data, pending, refresh} = await api.operator.paginate()

Error

[Vue warn]: Unhandled error during execution of setup function 
  at <Operator onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< undefined > key="/p/4/setting/operator" >
[nuxt] [request error] nuxt instance unavailable

When I comment on this line, the page opens // const {data, pending, refresh} = await api.operator.paginate()

ilyaDegtyarenko commented 2 years ago

And

Where can I make a handshake request before enabling middlewares? With the placement of the token in cookie for further inquiries.

In the server middleware or plugin, which is better? If I trying to do this in the app.vue then there it is done after processing the middleware and redirects are performed

hermesalvesbr commented 2 years ago

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.)

Sorry, i can´t understand yet.

  1. I can't to use async functions inside composables?
  2. I can´t call modules inside composables like const { getItemById } = useDirectusItems() ?
  3. I can´t get .env variables in composables?

If I can't do any of this, then what should I use in nuxt instead of composables?

danielroe commented 2 years ago

Yes, you can call other composables within composables.

Ideally you should not use async functions inside composables. Rather, you would likely want to return an async function so that it can be called within the setup function (or later).

You can get environment variables within composables. But mostly not after an async call.

uuf6429 commented 2 years ago

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.

danielroe commented 2 years ago

@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.

hermesalvesbr commented 2 years ago

Maybe this can be helpful

https://blog.devgenius.io/ecmascript-2022-is-officially-released-what-should-we-pay-attention-to-5e207ed61a46

rikbrowning commented 2 years ago

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?

Gustavodacrvi commented 1 year ago

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.