N
Nuxtā€¢3w ago
Kernel Panic

How are custom fetches used on a large scale project?

How are custom fetches used on a large scale project? I have read the custom fetch documentation on the Nuxt website. That and data-fetching. However, unless I missed something, I am struggling to put this into a large scale project. I currently have a pinia store, that uses a composable, that uses a plugin, that implements a custom fetch. While this does work to get the data, I can't seem to use useAsyncData with it, because I get an error if I try: "[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug" It's difficult to paste all of the code here because it is for my work project and client confidentiality and all that. In a nutshell, i'm trying to find a working solution where I can use a custom fetch, don't have to state the url in the page where the data is being fetched but instead it already being available a composable (eg. in the bookStore pinia store, I use an useMyApi composable which provides a fetchBooks function, which calls an endpoint using the custom fetch) An example:
<script setup lang="ts">
const { data } = await useFetch('/api/data')

async function handleFormSubmit() {
const res = await $fetch('/api/submit', {
method: 'POST',
body: {
// My form data
}
})
}
</script>
<script setup lang="ts">
const { data } = await useFetch('/api/data')

async function handleFormSubmit() {
const res = await $fetch('/api/submit', {
method: 'POST',
body: {
// My form data
}
})
}
</script>
becomes:
<script setup lang="ts">
const myStore = useMyStore();
const { data } = await myStore.fetchBooks();

async function handleFormSubmit(values) {
const res = await myStore.saveBook(values);
}
</script>
<script setup lang="ts">
const myStore = useMyStore();
const { data } = await myStore.fetchBooks();

async function handleFormSubmit(values) {
const res = await myStore.saveBook(values);
}
</script>
17 Replies
kapa.ai
kapa.aiā€¢3w ago
Hi @Kernel Panic. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- To implement custom fetches in a large-scale Nuxt project, you can create a custom API composable that wraps useFetch or useAsyncData. This approach allows you to centralize your API logic, handle authentication, and provide a consistent interface for data fetching across your application. Here's how you can structure this: 1. Create a custom API composable: First, create a composable that wraps useFetch with your custom logic. This can be placed in a file like composables/useAPI.ts:
import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
url: string | (() => string),
options?: UseFetchOptions<T>,
) {
return useFetch(url, {
...options,
$fetch: useNuxtApp().$api
})
}
import type { UseFetchOptions } from 'nuxt/app'

export function useAPI<T>(
url: string | (() => string),
options?: UseFetchOptions<T>,
) {
return useFetch(url, {
...options,
$fetch: useNuxtApp().$api
})
}
This example is based on the Custom useFetch documentation (https://nuxt.com/docs/guide/recipes/custom-usefetch#custom-usefetchuseasyncdata). 2. Create a plugin for your custom $fetch instance: Create a plugin to set up your custom $fetch instance with any global configurations (like headers or base URL). This can be placed in a file like plugins/api.ts:
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()

const api = $fetch.create({
baseURL: config.public.apiBase,
headers: {
// Add any global headers here
}
})

return {
provide: {
api
}
}
})
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()

const api = $fetch.create({
baseURL: config.public.apiBase,
headers: {
// Add any global headers here
}
})

return {
provide: {
api
}
}
})
3. Create store actions that use the custom API composable: In your Pinia store, you can now use the useAPI composable to create actions for fetching data:
import { defineStore } from 'pinia'
export const useBookStore = defineStore('book', {
state: () => ({
books: []
}),
actions: {
async fetchBooks() {
const { data, error } = await useAPI('/api/books')
if (data.value) {
this.books = data.value
}
return { data, error }
},
async saveBook(book) {
return await useAPI('/api/books', {
method: 'POST',
body: book
})
}
}
})
import { defineStore } from 'pinia'
export const useBookStore = defineStore('book', {
state: () => ({
books: []
}),
actions: {
async fetchBooks() {
const { data, error } = await useAPI('/api/books')
if (data.value) {
this.books = data.value
}
return { data, error }
},
async saveBook(book) {
return await useAPI('/api/books', {
method: 'POST',
body: book
})
}
}
})
4. Use the store in your components: Now you can use your store in your components without directly calling useFetch or specifying URLs:
<script setup lang="ts">
const bookStore = useBookStore()
const { data: books, error } = await bookStore.fetchBooks()

async function handleFormSubmit(values) {
const res = await bookStore.saveBook(values)
// Handle response
}
</script>
<script setup lang="ts">
const bookStore = useBookStore()
const { data: books, error } = await bookStore.fetchBooks()

async function handleFormSubmit(values) {
const res = await bookStore.saveBook(values)
// Handle response
}
</script>
This structure allows you to: 1. Centralize API logic in your custom useAPI composable. 2. Handle authentication and other global configurations in your API plugin. 3. Organize data fetching logic in your Pinia stores. 4. Keep your components clean and focused on presentation logic. Remember that useAPI, being a wrapper around useFetch, should be used in the same contexts where you'd use useFetch - in setup functions, plugins, or route middleware as mentioned in the documentation (https://nuxt.com/docs/getting-started/data-fetching#lazy). If you're still encountering issues with the Nuxt instance not being available, make sure you're calling these functions in the correct context. If the problem persists, you might need to use a different approach, such as creating a custom fetcher that doesn't rely on the Nuxt instance, as discussed in this Stack Overflow post (https://stackoverflow.com/questions/77391591). By following this structure, you should be able to implement custom fetches in a large-scale Nuxt project while maintaining clean and organized code. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.aiā€¢3w ago
<@246664150514663425> Kapa.ai is still learning and improving, please let me know how I did by reacting below.
Kernel Panic
Kernel PanicOPā€¢3w ago
following the above info, it still leads me to: [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables.
Tackleberry
Tackleberryā€¢3w ago
It's tricky without seeing any of your code, especially the way the plugin is implemented. A few things I could think of: - Why does the composable require access to the Nuxt instance in the first place? - Why does the composable need a separate plugin? Why not have the fetch in the composable? - Can you verify when / where the functionality is called? Is it actually within the lifecycle of the page / component - is the functionality used somewhere outside of your components (e.g. server side routes)? ah, and another one: - Do you await somewhere in your plugin / composable before calling anything nuxt-related? This could lead to context-loss
Kernel Panic
Kernel PanicOPā€¢3w ago
alright, let me paste it here quick I'm still stuck and i am suspecting the issue is inside the plugin itself
import {clearCookie} from "~/utils/clearCookies";

export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig();

const api = $fetch.create({
baseURL: config.public.xcelerateApiProxy as string,
onRequest({ options }) {
const identityStore = useIdentityStore();
const sessionIdKey = 'X-SessionId';
options.headers = new Headers(options.headers);
const sessionId = options.headers.get(sessionIdKey);
if (!identityStore.session || sessionId) return;
options.headers.set(sessionIdKey, identityStore.session.sessionID || '');
},
async onResponseError({ response }) {
if (response.status === 401) {
const config = useRuntimeConfig();
const authCookie = useCookie(config.public.xcelerateAuthCookie as string)
clearCookie(authCookie);

await nuxtApp.runWithContext(() => navigateTo({ name: 'login' }))
}
}
})

return {
provide: {
api
}
}
})
import {clearCookie} from "~/utils/clearCookies";

export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig();

const api = $fetch.create({
baseURL: config.public.xcelerateApiProxy as string,
onRequest({ options }) {
const identityStore = useIdentityStore();
const sessionIdKey = 'X-SessionId';
options.headers = new Headers(options.headers);
const sessionId = options.headers.get(sessionIdKey);
if (!identityStore.session || sessionId) return;
options.headers.set(sessionIdKey, identityStore.session.sessionID || '');
},
async onResponseError({ response }) {
if (response.status === 401) {
const config = useRuntimeConfig();
const authCookie = useCookie(config.public.xcelerateAuthCookie as string)
clearCookie(authCookie);

await nuxtApp.runWithContext(() => navigateTo({ name: 'login' }))
}
}
})

return {
provide: {
api
}
}
})
Here is the plugin Here is the store
export const useStatusStore = defineStore('statuses', () => {
const url = '/api/statuses';

const fetchAll = () => {
const { $api } = useNuxtApp();
return $api(url);
};

return {
fetchAll
}
});
export const useStatusStore = defineStore('statuses', () => {
const url = '/api/statuses';

const fetchAll = () => {
const { $api } = useNuxtApp();
return $api(url);
};

return {
fetchAll
}
});
then on the page
<script setup>
const statusStore = useStatusStore();
const { data, refresh, status, error } = await useAsyncData(async () => {
return await statusStore.fetchAll();
});
</script>
<script setup>
const statusStore = useStatusStore();
const { data, refresh, status, error } = await useAsyncData(async () => {
return await statusStore.fetchAll();
});
</script>
Just loading the page, i get the error [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables. But when calling the "refresh" function it works as expected. So it only works on refresh, but i need it loaded async on client side Multiple calls are happening on this page - 5 in total. So i am using a Promise.all. But I have simplified it to resolve this error i keep getting. I am suspecting it is in the custom fetch plugin that is causing the issue Thanks for your assistance
Tackleberry
Tackleberryā€¢3w ago
why are you creating a function where you return the fetchAll call? And where is it called? Could you verify if this call triggers the warning? Because if this fetchAll function is called somewhere out of context, it might be the issue
Kernel Panic
Kernel PanicOPā€¢3w ago
It's being called on the page
<script setup>
const statusStore = useStatusStore();
const { data, refresh, status, error } = await useAsyncData(async () => {
return await statusStore.fetchAll();
});
</script>
<script setup>
const statusStore = useStatusStore();
const { data, refresh, status, error } = await useAsyncData(async () => {
return await statusStore.fetchAll();
});
</script>
It is done this way to abstract away the need to pass the url on the page. All documentation that i come across is using the '/api/myEndpoint' kind of parameter within the page. If i can localise it to a store, like fetching data, posting data, saving data, all from within the same store, this would be ideal In short, I need: - a custom fetch to deal with headers and auth session data - a store to hold related data and url for related end points - all calls to be async for speed on client side and for some reason, just can't shake the "A composable that requires access to the Nuxt instance was called outside of a plugin, " error Thanks again for your help šŸ™‚
Tackleberry
Tackleberryā€¢3w ago
no problem, don't have too much time so it's a few shots in the dark šŸ˜„ have you tried supplying the nuxt app through your handler? If it works on refresh but not on page load I assume useNuxtApp() does not work on initial load because it's out of context (I assume because of some async shenanigans). So maybe try supplying the nuxt app via the handler function as it might have a nuxt app according to the type. So something like
await useAsyncData(async (nuxtApp) => {
return await statusStore.fetchAll(nuxtApp);
});
await useAsyncData(async (nuxtApp) => {
return await statusStore.fetchAll(nuxtApp);
});
and then check if nuxtApp is defined in your fetchAll and only try useNuxtApp if it isn't if that's no good, try setting + exposing api earlier and not in the fetchAll? Because you definitely should have a nuxt instance available in the setup of your store. Like this:
export const useStatusStore = defineStore('statuses', () => {
const url = '/api/statuses';
const { $api } = useNuxtApp(); // expose in store setup

const fetchAll = () => {
return $api(url);
};

return {
fetchAll
}
});
export const useStatusStore = defineStore('statuses', () => {
const url = '/api/statuses';
const { $api } = useNuxtApp(); // expose in store setup

const fetchAll = () => {
return $api(url);
};

return {
fetchAll
}
});
Kernel Panic
Kernel PanicOPā€¢3w ago
let me check quick so i tried this directly on the page:
const { data, refresh, status, error } = await useAsyncData(async ({ $xcelerate }) => {
return await $xcelerate('/api/ACBBatchStatu');
});
const { data, refresh, status, error } = await useAsyncData(async ({ $xcelerate }) => {
return await $xcelerate('/api/ACBBatchStatu');
});
Sorry for the name change, was trying to keep it more generic but bashing my head against a wall here lol. in theory, that should work. But i still get " error [nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables." which leads me to think it is the loading pinia store in the plugin that is causing the issue:
onRequest({ options }) {
const identityStore = useIdentityStore();
const sessionIdKey = 'X-SessionId';
options.headers = new Headers(options.headers);
const sessionId = options.headers.get(sessionIdKey);
if (!identityStore.session || sessionId) return;
options.headers.set(sessionIdKey, identityStore.session.sessionID || '');
},
onRequest({ options }) {
const identityStore = useIdentityStore();
const sessionIdKey = 'X-SessionId';
options.headers = new Headers(options.headers);
const sessionId = options.headers.get(sessionIdKey);
if (!identityStore.session || sessionId) return;
options.headers.set(sessionIdKey, identityStore.session.sessionID || '');
},
I think it is the useIdentityStore that is the "composable"
Tackleberry
Tackleberryā€¢3w ago
maybe try pulling it out of the onRequest? Also, does it not supply any lines or stack traces where it complains about?
Kernel Panic
Kernel PanicOPā€¢3w ago
{"data":null,"status":"error","error":{"message":"[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at `https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables`.","statusCode":500}}
{"data":null,"status":"error","error":{"message":"[nuxt] A composable that requires access to the Nuxt instance was called outside of a plugin, Nuxt hook, Nuxt middleware, or Vue setup function. This is probably not a Nuxt bug. Find out more at `https://nuxt.com/docs/guide/concepts/auto-imports#vue-and-nuxt-composables`.","statusCode":500}}
That is what comes back from the useAsyncData composable
Tackleberry
Tackleberryā€¢3w ago
this is in the browser, I assume. What about the server log? And the approach of pulling stuff up into a layer where context should definitely be available?
Kernel Panic
Kernel PanicOPā€¢3w ago
Nothing happening in terminal. I'm using nuxt dev
ā„¹ page reload plugins/xcelerate.ts (x2) 3:34:51 PM
āœ” Vite server hmr 5 files in 88.18ms
ā„¹ hmr update /pages/acb-dashboard.vue 3:35:52 PM
āœ” Vite server hmr 10 files in 107.671ms
ā„¹ hmr update /pages/acb-dashboard.vue (x2) 3:36:29 PM
āœ” Vite server hmr 10 files in 111.273ms
ā„¹ page reload plugins/xcelerate.ts (x2) 3:34:51 PM
āœ” Vite server hmr 5 files in 88.18ms
ā„¹ hmr update /pages/acb-dashboard.vue 3:35:52 PM
āœ” Vite server hmr 10 files in 107.671ms
ā„¹ hmr update /pages/acb-dashboard.vue (x2) 3:36:29 PM
āœ” Vite server hmr 10 files in 111.273ms
without the sessionId in my header, getting 401 unauthorised. Maybe there is another way of dealing with the headers Common use cases, my inexperience with nuxt seems to be the biggest bottle neck
Tackleberry
Tackleberryā€¢3w ago
still, what about pulling stuff up a layer? Do you know what I mean?
Kernel Panic
Kernel PanicOPā€¢3w ago
i don't know what you mean, i'm sorry
Tackleberry
Tackleberryā€¢3w ago
No problem! I'm referring to the approach outlined in this and this message. The idea is to not call the nuxt-context-dependent functions within the function handler itself, but rather define them in the setup part of your composable, where you are certain to have the necessary context. Then you can call them within your handlers. Let's compare yours and mine. Yours:
export const useStatusStore = defineStore('statuses', () => {
const url = '/api/statuses';

const fetchAll = () => {
// Trying to call useNuxtApp from within an event handler. It might not have nuxt context available because it is executed at any random time.
const { $api } = useNuxtApp();
return $api(url);
};

return {
fetchAll
}
});
export const useStatusStore = defineStore('statuses', () => {
const url = '/api/statuses';

const fetchAll = () => {
// Trying to call useNuxtApp from within an event handler. It might not have nuxt context available because it is executed at any random time.
const { $api } = useNuxtApp();
return $api(url);
};

return {
fetchAll
}
});
to mine:
export const useStatusStore = defineStore('statuses', () => {
const url = '/api/statuses';
// call useNuxtApp in the store setup function, which is executed during component and page setups, so nuxt context should be available
// + added below: const { $api } = useNuxtApp(); // expose in store setup
const { $api } = useNuxtApp(); // expose in store setup

const fetchAll = () => {
// - removed: const { $api } = useNuxtApp();
return $api(url);
};

return {
fetchAll
}
});
export const useStatusStore = defineStore('statuses', () => {
const url = '/api/statuses';
// call useNuxtApp in the store setup function, which is executed during component and page setups, so nuxt context should be available
// + added below: const { $api } = useNuxtApp(); // expose in store setup
const { $api } = useNuxtApp(); // expose in store setup

const fetchAll = () => {
// - removed: const { $api } = useNuxtApp();
return $api(url);
};

return {
fetchAll
}
});
The same approach could be used in your onRequest handler:
onRequest({ options }) {
// trying to call useIdentityStore from within an onRequest function, which might be executed at any random time. nuxt context might not be available at that point
const identityStore = useIdentityStore();
const sessionIdKey = 'X-SessionId';
options.headers = new Headers(options.headers);
const sessionId = options.headers.get(sessionIdKey);
if (!identityStore.session || sessionId) return;
options.headers.set(sessionIdKey, identityStore.session.sessionID || '');
},
onRequest({ options }) {
// trying to call useIdentityStore from within an onRequest function, which might be executed at any random time. nuxt context might not be available at that point
const identityStore = useIdentityStore();
const sessionIdKey = 'X-SessionId';
options.headers = new Headers(options.headers);
const sessionId = options.headers.get(sessionIdKey);
if (!identityStore.session || sessionId) return;
options.headers.set(sessionIdKey, identityStore.session.sessionID || '');
},
instead try this:
// define identityStore in page / component / composable setup function, where nuxt context is available
const identityStore = useIdentityStore();

// ...
onRequest({ options }) {
at any random time. nuxt context might not be available at that point
const sessionIdKey = 'X-SessionId';
options.headers = new Headers(options.headers);
const sessionId = options.headers.get(sessionIdKey);
if (!identityStore.session || sessionId) return;
options.headers.set(sessionIdKey, identityStore.session.sessionID || '');
},
// define identityStore in page / component / composable setup function, where nuxt context is available
const identityStore = useIdentityStore();

// ...
onRequest({ options }) {
at any random time. nuxt context might not be available at that point
const sessionIdKey = 'X-SessionId';
options.headers = new Headers(options.headers);
const sessionId = options.headers.get(sessionIdKey);
if (!identityStore.session || sessionId) return;
options.headers.set(sessionIdKey, identityStore.session.sessionID || '');
},
Kernel Panic
Kernel PanicOPā€¢3w ago
Thank you very much! I need to step away for a bit now, but i'll check this out and revert back asap. Thanks again!
Want results from more Discord servers?
Add your server