Skip to content

feat: add a factory function for useFetch and useAsyncData #32300

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

cernymatej
Copy link
Member

@cernymatej cernymatej commented Jun 4, 2025

Warning

This is an initial draft of the PR. Treat it as a proof of concept and a starting point for discussions about the ideal API. None of the points mentioned below are final - everything is very much a work in progress.

🔗 Linked issue

resolves #14736
resolves #14936
resolves #28792
resolves #28485
resolves #24157
fixes #26275

📚 Description

This PR introduces a way to create custom useFetch and useAsyncData functions with user-defined default options.

These custom instances can be configured with either static options or a dynamic getter, which can be used to access values from runtimeConfig, for example.

// composables/custom-fetch.ts

export const useApiFetch = createUseFetch(() => {
  const runtimeConfig = useRuntimeConfig()

  return {
    defaults: {
      baseURL: runtimeConfig.public.baseApiUrl,
    },
  }
})

export const useClientFetch = createUseFetch({
  defaults: {
    server: false,
  },
})

How it works

Both composables rely on automatic key injection. For this reason, it was necessary to create a mechanism to automatically register these new custom instances to optimization.keyedComposables. This registration occurs during Nuxt initialization, where the composables folder is scanned for usages of these factory functions. Each export is processed, and the corresponding composable identifier is registered for key injection.

The factory functions should be treated more like compiler macros. Because of this, there are only a few places where they can be used - specifically, as exports from files in the composables directory. To warn users about incorrect usage, the runtime version of these factory functions simply throws an error. Only when they are correctly recognized and processed during compilation are they replaced with the actual factory functions (which are prefixed with an underscore, e.g., _createUseFetch()).

I plan to create a guide for manual creation later on. I believe it's important to ensure this works with auto-imports fully disabled.

🔖 Additional changes

This PR also allows modules to extend the keyedComposables config, which wasn't possible before. (#26275) With this change, I believe there's a greater need to introduce a more fine-grained approach to defining keyed composables. Modules could potentially add many keyed functions, which might collide with user-defined functions or those from other modules.

I propose that the source option - already available for keyedComposables (though never documented anywhere) - should be treated as the path to the file from which the composable originates. This would allow the build plugin to target only the specific functions. I would even consider making this option required to prevent accidental errors, and possibly removing the option to pass a regex to it - since the function will always be defined in a single file, so I don’t really see the need for a regex - in my opition, it only makes the definition “less specific”. (This hasn’t been implemented yet, since it would be considered a breaking change… maybe we can include it in Nuxt 4? The extension of the string comparison - which has been implemented here - shouldn’t break anything, though)

💬 Additional points to discuss

  • Should provided options like onRequest completely override the defaults when calling the composable, or should they merge with them? We could potentially separate these two strategies under different keys in the factory configuration object. Currently, everything is grouped under defaults, but I'm considering introducing a second key (e.g., merge) that would merge the options. This would allow users to define an onRequest handler when calling the composable, while still executing the one defined in the factory function. (similar issue Allow having interceptos from both defaults and per request unjs/ofetch#319)
  • Could it be useful for module authors to have the ability to define factory functions for their own keyed composables? Should we provide a way to do it easily via @nuxt/kit?

  • Should we make the source option in the keyedComposables object required and remove the possibility to pass a regex?
  • Some time ago, I recall discussions about having useFetch pass cookies from the server to the client (by default?). Is this something we want to tackle in this PR, or should we revisit it later? (Simplify Set-Cookie header forwarding #32306)
  • useFetch only prevents multiple requests when key option is set #28169
  • Should we make UseFetchOptions extendable? This would allow users to add custom configuration keys to their useFetch instances, which they could then handle in the dynamic getter version of the factory function (by applying different logic in request hooks, etc.) - related: Add a resetAfter option to useFetch to automatically reset the state after a specified time. #28792

🚧 TODO

  • decide on the final public API and function names
  • tidy up the implementation & add missing checks in TODOs
  • decide whether to expose @nuxt/kit utilities for creating factories
  • export the internal useFetch utilities like generateOptionSegments for better customization
  • come up with a guide for manual creation of the custom instances (without auto imports and compiler scanning)
  • improve the AST walking performance with Support for skipping nodes oxc-project/oxc-walker#106
  • check auto-imports for keyed composable specifiers too
  • add a factory for useAsyncData()
  • add tests
  • add docs (and fix the documented types of useFetch)

Copy link

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

Copy link

pkg-pr-new bot commented Jun 4, 2025

Open in StackBlitz

@nuxt/kit

npm i https://pkg.pr.new/@nuxt/kit@32300

nuxt

npm i https://pkg.pr.new/nuxt@32300

@nuxt/rspack-builder

npm i https://pkg.pr.new/@nuxt/rspack-builder@32300

@nuxt/schema

npm i https://pkg.pr.new/@nuxt/schema@32300

@nuxt/vite-builder

npm i https://pkg.pr.new/@nuxt/vite-builder@32300

@nuxt/webpack-builder

npm i https://pkg.pr.new/@nuxt/webpack-builder@32300

commit: d4ba88e

Copy link

codspeed-hq bot commented Jun 4, 2025

CodSpeed Performance Report

Merging #32300 will not alter performance

Comparing cernymatej:feat-use-fetch-factory (d4ba88e) with main (42114f4)

Summary

✅ 10 untouched benchmarks

@cernymatej cernymatej requested a review from pi0 June 4, 2025 22:18
@maximepvrt
Copy link
Contributor

@cernymatej It would also be great to support creating a custom $fetch instance, for example to use when submitting a form (with different headers, base URL, …).

@cernymatej
Copy link
Member Author

@maximepvrt that is already possible in using $fetch.create(/* defaults */) - or am I missing something?

https://nuxt.com/docs/guide/recipes/custom-usefetch#custom-fetch

@maximepvrt
Copy link
Contributor

maximepvrt commented Jun 5, 2025

@cernymatej Yes, you're right. Tt's possible via $fetch.create, but currently we have to define it as a plugin and access it through const { $api } = useNuxtApp(), which isn’t ideal for DX compared to auto-imported composables.

It would be great if we could also easily create a custom useFetch from a $fetch instance, something like:

export const useClientFetch = createUseFetch({ fetch: $api })

or

export const useClientFetch = createUseFetch($api)

@cernymatej
Copy link
Member Author

You can already register the custom $fetch instance to be auto-imported too. Either by putting it inside the utils folder, or by configuring the auto-import manually.

// utils/myFetch.ts
export const $myFetch = $fetch.create()  // will be auto-imported

as for providing a custom $fetch instance to these custom useFetch instances, that is also already possible in this PR:

// composables/myUseFetch.ts
import { $myFetch } from '../utils/myFetch'

export const myUseFetch = createUseFetch({ $fetch: $myFetch })

@maximepvrt
Copy link
Contributor

Perfect 🥰🥰

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy