Skip to content

Commit

Permalink
feat(useRouteQuery,useRouteParams): new transform handler (#2191)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
edumudu and antfu committed Apr 13, 2023
1 parent 23b9a34 commit d23ca30
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 31 deletions.
9 changes: 9 additions & 0 deletions packages/router/_types.ts
@@ -1,6 +1,8 @@
import type { MaybeRef } from '@vueuse/shared'
import type { useRoute, useRouter } from 'vue-router'

export type RouterQueryValue = null | undefined | string | string[]

export interface ReactiveRouteOptions {
/**
* Mode to update the router query, ref is also acceptable
Expand All @@ -19,3 +21,10 @@ export interface ReactiveRouteOptions {
*/
router?: ReturnType<typeof useRouter>
}

export interface ReactiveRouteOptionsWithTransform<V, R> extends ReactiveRouteOptions {
/**
* Function to transform data before return
*/
transform?: (val: V) => R
}
2 changes: 2 additions & 0 deletions packages/router/useRouteParams/index.md
Expand Up @@ -15,6 +15,8 @@ const userId = useRouteParams('userId')

const userId = useRouteParams('userId', '-1') // or with a default value

const userId = useRouteParams('page', '1', { transform: Number }) // or transforming value

console.log(userId.value) // route.params.userId
userId.value = '100' // router.replace({ params: { userId: '100' } })
```
24 changes: 24 additions & 0 deletions packages/router/useRouteParams/index.test.ts
@@ -0,0 +1,24 @@
import { useRouteParams } from '.'

describe('useRouteQuery', () => {
const getRoute = (params: Record<string, any> = {}) => ({
query: {},
fullPath: '',
hash: '',
matched: [],
meta: {},
name: '',
params: { id: '1', ...params },
path: '',
redirectedFrom: undefined,
})

it('should return transformed value', () => {
const router = {} as any
const route = getRoute()

const id = useRouteParams('id', '1', { transform: Number, route, router })

expect(id.value).toBe(1)
})
})
39 changes: 26 additions & 13 deletions packages/router/useRouteParams/index.ts
Expand Up @@ -2,27 +2,40 @@ import type { Ref } from 'vue-demi'
import { computed, nextTick } from 'vue-demi'
import { useRoute, useRouter } from 'vue-router'
import { toValue } from '@vueuse/shared'
import type { ReactiveRouteOptions } from '../_types'
import type { ReactiveRouteOptionsWithTransform, RouterQueryValue } from '../_types'

export function useRouteParams(name: string): Ref<null | string | string[]>
export function useRouteParams<T extends null | undefined | string | string[] = null | string | string[]>(name: string, defaultValue?: T, options?: ReactiveRouteOptions): Ref<T>
export function useRouteParams<T extends string | string[]>(
export function useRouteParams(
name: string
): Ref<null | string | string[]>

export function useRouteParams<
T extends RouterQueryValue = RouterQueryValue,
K = T,
>(
name: string,
defaultValue?: T,
{
options?: ReactiveRouteOptionsWithTransform<T, K>
): Ref<K>

export function useRouteParams<
T extends RouterQueryValue = RouterQueryValue,
K = T,
>(
name: string,
defaultValue?: T,
options: ReactiveRouteOptionsWithTransform<T, K> = {},
): Ref<K> {
const {
mode = 'replace',
route = useRoute(),
router = useRouter(),
}: ReactiveRouteOptions = {},
) {
transform = value => value as any as K,
} = options

return computed<any>({
get() {
const data = route.params[name]
if (data == null)
return defaultValue ?? null
if (Array.isArray(data))
return data.filter(Boolean)
return data
const data = route.params[name] ?? defaultValue
return transform(data as T)
},
set(v) {
nextTick(() => {
Expand Down
2 changes: 2 additions & 0 deletions packages/router/useRouteQuery/index.md
Expand Up @@ -15,6 +15,8 @@ const search = useRouteQuery('search')

const search = useRouteQuery('search', 'foo') // or with a default value

const page = useRouteQuery('page', '1', { transform: Number }) // or transforming value

console.log(search.value) // route.query.search
search.value = 'foobar' // router.replace({ query: { search: 'foobar' } })
```
34 changes: 34 additions & 0 deletions packages/router/useRouteQuery/index.test.ts
@@ -0,0 +1,34 @@
import { useRouteQuery } from '.'

describe('useRouteQuery', () => {
const getRoute = (query: Record<string, any> = {}) => ({
query: {
search: 'vue3',
page: '1',
...query,
},
fullPath: '',
hash: '',
matched: [],
meta: {},
name: '',
params: {},
path: '',
redirectedFrom: undefined,
})

it('should return transformed value', () => {
const router = {} as any
const route = getRoute()
const transform = Number
const toArray = (param: string | string[] | null) => Array.isArray(param) ? param : [param]

const page = useRouteQuery('page', '1', { transform, route, router })
const perPage = useRouteQuery('perPage', '15', { transform, route, router })
const tags = useRouteQuery('tags', [], { transform: toArray, route: getRoute({ tags: 'vite' }), router })

expect(page.value).toBe(1)
expect(perPage.value).toBe(15)
expect(tags.value).toEqual(['vite'])
})
})
49 changes: 31 additions & 18 deletions packages/router/useRouteQuery/index.ts
@@ -1,37 +1,50 @@
import type { Ref } from 'vue-demi'
import { computed, nextTick } from 'vue-demi'
import { useRoute, useRouter } from 'vue-router'
import { toValue } from '@vueuse/shared'
import type { ReactiveRouteOptions } from '../_types'
import { useRoute, useRouter } from 'vue-router'
import type { ReactiveRouteOptionsWithTransform, RouterQueryValue } from '../_types'

let queue: Record<string, any> = {}
let _queue: Record<string, any> = {}

export function useRouteQuery(name: string): Ref<null | string | string[]>
export function useRouteQuery<T extends null | undefined | string | string[] = null | string | string[]>(name: string, defaultValue?: T, options?: ReactiveRouteOptions): Ref<T>
export function useRouteQuery<T extends string | string[]>(
export function useRouteQuery(
name: string
): Ref<null | string | string[]>

export function useRouteQuery<
T extends RouterQueryValue = RouterQueryValue,
K = T,
>(
name: string,
defaultValue?: T,
{
options?: ReactiveRouteOptionsWithTransform<T, K>
): Ref<K>

export function useRouteQuery<
T extends RouterQueryValue = RouterQueryValue,
K = T,
>(
name: string,
defaultValue?: T,
options: ReactiveRouteOptionsWithTransform<T, K> = {},
): Ref<K> {
const {
mode = 'replace',
route = useRoute(),
router = useRouter(),
}: ReactiveRouteOptions = {},
) {
transform = value => value as any as K,
} = options

return computed<any>({
get() {
const data = route.query[name]
if (data == null)
return defaultValue ?? null
if (Array.isArray(data))
return data.filter(Boolean)
return data
const data = route.query[name] ?? defaultValue
return transform(data as T)
},
set(v) {
queue[name] = (v === defaultValue || v === null) ? undefined : v
_queue[name] = (v === defaultValue || v === null) ? undefined : v

nextTick(() => {
router[toValue(mode)]({ ...route, query: { ...route.query, ...queue } })
nextTick(() => queue = {})
router[toValue(mode)]({ ...route, query: { ...route.query, ..._queue } })
nextTick(() => _queue = {})
})
},
})
Expand Down

0 comments on commit d23ca30

Please sign in to comment.