diff --git a/docs/3.api/2.composables/use-route-query.md b/docs/3.api/2.composables/use-route-query.md new file mode 100644 index 0000000000..766ce4e0b5 --- /dev/null +++ b/docs/3.api/2.composables/use-route-query.md @@ -0,0 +1,134 @@ +--- +title: "useRouteQuery" +description: The useRouteQuery composable simplifies working with query parameters. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/nuxt/blob/main/packages/nuxt/src/app/composables/router.ts + size: xs +--- + +::note +You can use `useRouteQuery` to seamlessly retrieve and update query parameters directly within your application. +:: + +## Example + +In the following example, we create a dynamic search interface. The input field updates the `q` query parameter in the URL, which is then used to fetch data from the Google Books API. + +```vue [~/pages/index.vue] + + + +``` + +## Features + +- **Retrieve Query Parameters**: Access specific query parameters with default values. +- **Update Query Parameters**: Dynamically modify query parameters, reflecting changes in the URL. +- **Automatic Cleanup**: Remove empty or default parameters with configurable options. + +## API + +### `useRouteQuery(param, defaultValue, options)` + +#### Parameters + +- `param` (String): The name of the query parameter to manage. +- `defaultValue` (String): The default value if the query parameter is not present. +- `options` (Object): + - `removeEmpty` (Boolean): Remove parameters with empty values. Default: `true`. + - `removeDefault` (Boolean): Remove parameters equal to the default value. Default: `true`. + +#### Returns + +A `computed` object with: + +- **`get`**: Retrieves the current value of the query parameter or the default value. +- **`set`**: Updates the query parameter in the URL. + +## Additional Examples + +### Basic Retrieval + +Retrieve a query parameter with a default value fallback: + +```javascript +const searchQuery = useRouteQuery('search', 'defaultSearch'); +console.log(searchQuery.value); // Outputs: 'defaultSearch' if `?search` is not in the URL +``` + +### Dynamic Updates + +Update the query parameter dynamically: + +```javascript +const searchQuery = useRouteQuery('search', 'defaultSearch'); +searchQuery.value = 'newSearchValue'; +// URL updates to include `?search=newSearchValue` +searchQuery.value = ''; +// URL removes `?search` due to `removeEmpty: true` +``` + +### Custom Options + +Customize behavior with options: + +```javascript +const filterQuery = useRouteQuery('filter', 'all', { + removeEmpty: false, + removeDefault: false, +}); +filterQuery.value = ''; +// URL remains `?filter=` because `removeEmpty` is `false` +filterQuery.value = 'all'; +// URL remains `?filter=all` because `removeDefault` is `false` +``` + +### Toggle Query Parameter + +Manage a toggle state with a query parameter: + +```javascript +const isVisible = useRouteQuery('visible', false); +function toggleVisibility() { + isVisible.value = !isVisible.value; +} +// Updates the URL to `?visible=true` or removes `?visible` +``` + +### Managing Multiple Query Parameters + +Handle multiple query parameters simultaneously: + +```javascript +const category = useRouteQuery('category', 'all'); +const sort = useRouteQuery('sort', 'asc'); +category.value = 'books'; +sort.value = 'desc'; +// URL updates to `?category=books&sort=desc` +``` + +## Notes + +- Ensure that `useRouteQuery` is used within a Nuxt component or setup function to access `useRoute` and `useRouter`. +- Query updates replace the entire query object. Ensure no unintended parameters are overwritten. + +## Limitations + +- This composable does not handle deeply nested query parameters. +- It is designed for flat query structures only. + +::read-more{icon="i-simple-icons-vuedotjs" to="https://vuejs.org/api/reactivity-core.html#computed"} diff --git a/packages/nuxt/src/app/composables/router.ts b/packages/nuxt/src/app/composables/router.ts index 3f95ffe8ec..c1b705cf55 100644 --- a/packages/nuxt/src/app/composables/router.ts +++ b/packages/nuxt/src/app/composables/router.ts @@ -1,5 +1,5 @@ -import { getCurrentInstance, hasInjectionContext, inject, onScopeDispose } from 'vue' -import type { Ref } from 'vue' +import { computed, getCurrentInstance, hasInjectionContext, inject, onScopeDispose } from 'vue' +import type { ComputedRef, Ref } from 'vue' import type { NavigationFailure, NavigationGuard, RouteLocationNormalized, RouteLocationRaw, Router, useRoute as _useRoute, useRouter as _useRouter } from 'vue-router' import { sanitizeStatusCode } from 'h3' import { hasProtocol, isScriptProtocol, joinURL, withQuery } from 'ufo' @@ -280,3 +280,40 @@ export function encodeURL (location: string, isExternalHost = false) { } return url.toString() } + +interface UseRouteQueryOptions { + removeEmpty?: boolean + removeDefault?: boolean +} + +export function useRouteQuery ( + param: string, + defaultValue: string | null = null, + options: UseRouteQueryOptions = { + removeEmpty: true, + removeDefault: true, + }, +): ComputedRef { + const route = useRoute() + const router = useRouter() + + return computed({ + get () { + const value = route.query[param] + return (value as string | undefined) ?? defaultValue + }, + set (value: string | null) { + const newQuery = { ...route.query, [param]: value } + + if (options.removeEmpty && (value === null || value === undefined || value === '')) { + delete newQuery[param] + } + + if (options.removeDefault && value === defaultValue) { + delete newQuery[param] + } + + router.push({ query: newQuery }) + }, + }) +}