[Url] Add `useUrlQuerySync` function
kiaking opened this issue · 0 comments
Often, we need to have a way to sync URL query params with ref or reactive. Would be nice to have good function does that.
Example usage
Let's say we have this kind of api call.
// Option to pass in to the API request.
// Each value is the default value.
const options = reactive({
page: 1,
conditions: {
name: 'John'
}
})
const { data } = useQuery(options)
function showNextPage() {
options.page++
}
And then, we would like to "sync" this option to URL query params so that we can create a direct link to this conditions.
const options = reactive({
page: 1,
conditions: {
name: 'John'
}
})
// Sync with query.
useUrlQuerySync(options, {
casts: {
page: Number
}
})
const { data } = useQuery(options)
function showNextPage() {
options.page++
}
Detailed examples
Sync should be by-directional.
// when URL is
// `https://example.com`
const options = reactive({ page: 1 })
useUrlQuerySync(options)
// -> https://example.com
// If the query is default value, do not show params on URL.
options.page++
// -> https://example.com?page=2
// If query differs from initial value, add params.
// If user access page with
// https://example.com?page=5
const options = reactive({ page: 1 })
useUrlQuerySync(options)
options.page // <- 5. Should be synced with URL.
Options
It should be able to cast or mutation the query params, because they are all string
.
useUrlQuerySync(options, {
casts: {
page: (value) => Number(value)
}
})
It should be able to exclude certain params from showing up on URL.
Note
This option might be too much... we could always remove the values before passing them into the function 🤔
useUrlQuerySync(options, {
// Do not display `perPage` option in URL.
exclude: ['perPage']
})
Current implementation
We use something like this in our apps. Would be great to organize this one.
Warning
Note that argument is different from the above examples because I changed it.
export interface UseUrlQuerySyncOptions {
state: Record<string, any>
casts?: Record<string, (value: any) => any>
exclude?: string[]
}
export function useUrlQuerySync({ state, casts = {}, exclude = [] }: UseQuerySyncOptions): void {
const router = useRouter()
const route = useRoute()
const initialState = cloneDeep(state)
syncQueryToState()
watch(() => state, syncStateToQuery, { deep: true })
function syncStateToQuery(): void {
if (isEqual(state, initialState)) {
router.replace({ query: {} })
return
}
const newState = cloneDeep(state)
exclude.forEach((path) => unset(newState, path))
router.replace({ query: newState })
}
function syncQueryToState(): void {
const newState = cloneDeep(state)
mergeWith(newState, route.query, (stateValue, _queryValue, key) => {
if (!isNaN(Number(key))) {
return
}
if (stateValue === undefined) {
return '__REMOVE__'
}
})
mergeWith({}, newState, (_dummyValue, stateValue, key, _dummyParent, stateParent) => {
if (stateValue === '__REMOVE__') {
delete stateParent[key]
}
})
for (const path in casts) {
update(newState, path, casts[path])
}
exclude.forEach((path) => unset(newState, path))
mergeWith(state, newState)
}
}
Alternatives
The alternative API ideas are welcome as well!
Consideration
Maybe we could use useUrlSearchParams from vueuse? But not sure if this is OK when using Vue Router 🤔 (directly modifying URL no going through router API).