🕶
vue-condition-watcher
Introduction
Vue composition API for automatic data fetching and easily control conditions
requires Node.js 12.0.0 or higher.
Features
cd examples/vue3
yarn
yarn serve
cd examples/vue2
yarn
yarn serve
👉 Online demo with vue-infinite-scroll
Getting Started
Installation
In your project
yarn add vue-condition-watcher
Or with npm
npm install vue-condition-watcher
CDN
https://unpkg.com/vue-condition-watcher/dist/index.js
Quick Start
This is a simple example for vue-next
and vue-router-next
First you need to create a fetcher function, use the native fetch
or libs like Axios. Then import useConditionWatcher
and start using it.
createApp({
template: `
<div class="filter">
<input v-model="conditions.name">
<button @click="execute">Refetch</button>
</div>
<div class="container">
{{ !loading ? data : 'Loading...' }}
</div>
<div v-if="error">{{ error }}</div>
`,
setup() {
const fetcher = params => axios.get('/user/', {params})
const { conditions, data, loading, error } = useConditionWatcher(
{
fetcher,
conditions: {
name: ''
},
},
{
sync: 'router'
}
)
return { conditions, data, loading, error }
},
})
.provide('router', router)
.use(router)
.mount(document.createElement('div'))
You can use the value of data
, error
, and loading
to determine the current state of the request.
When the conditions.name
value changes, will fire the lifecycle
to fetching data again.
Set router
instance at provider
, and use query option of sync sync: 'router'
. Will store the conditions within the URL hash every time conditions change.
Basic Usage
const { conditions, data, error, loading, execute, resetConditions, onConditionsChange } = useConditionWatcher(config, queryOptions)
Execute Fetch
conditions
is reactive proxy, easy execute fetch when conditions
value changed
const { conditions } = useConditionWatcher({
fetcher,
conditions: {
page: 0
},
defaultParams: {
opt_expand: 'date'
}
})
conditions.page = 1 // fetch data with payload { page: 1, opt_expand: 'date' }
conditions.page = 2 // fetch data with payload { page: 2, opt_expand: 'date' }
Just call execute
function to send a request if you need.
const { conditions, execute: refetch } = useConditionWatcher({
fetcher,
conditions: {
page: 0
},
defaultParams: {
opt_expand: 'date'
}
})
refetch() // fetch data with payload { page: 0, opt_expand: 'date' }
Force update conditions in time.
const { conditions, resetConditions } = useConditionWatcher({
fetcher,
immediate: false,
conditions: {
page: 0,
name: '',
date: []
},
})
// initial conditions then fire onConditionsChange event
Object.assign(conditions, {
name: 'runkids',
date: ['2022-01-01', '2022-01-02']
})
// Reset conditions
function reset () {
Object.assign(conditions, {
page: 0,
name: '',
date: []
})
// Or you can just use `resetConditions` function to initial value.
resetConditions()
}
Conditions Change Event
onConditionsChange
can help you handle conditions changed.
Will return new value and old value.
const { conditions, onConditionsChange } = useConditionWatcher({
fetcher,
conditions: {
page: 0
},
})
conditions.page = 1
onConditionsChange((conditions, preConditions)=> {
console.log(conditions) // { page: 1}
console.log(preConditions) // { page: 0}
})
Fetch Event
The onFetchResponse
, onFetchError
and onFetchFinally
will fire on fetch request.
const { onFetchResponse, onFetchError, onFetchFinally } = useConditionWatcher(config)
onFetchResponse((response) => {
console.log(response)
})
onFetchError((error) => {
console.error(error)
})
onFetchFinally(() => {
//todo
})
Prevent Request
Setting the immediate
to false will prevent the request until the execute
function called or conditions changed.
const { execute } = useConditionWatcher({
fetcher,
conditions,
immediate: false,
})
execute()
Intercepting Request
The beforeFetch
let you modify conditions before fetch, or you can call cancel
function to stop fetch.
useConditionWatcher({
fetcher,
conditions: {
date: ['2022/01/01', '2022/01/02']
},
initialData: [],
async beforeFetch(conditions, cancel) {
// await something
await doSomething ()
// conditions is an object clone copy from config.conditions
const {date, ...baseConditions} = conditions
const [after, before] = date
baseConditions.created_at_after = after
baseConditions.created_at_before = before
return baseConditions
}
})
The afterFetch
can intercept the response before data updated
const { data } = useConditionWatcher({
fetcher,
conditions,
async afterFetch(response) {
//response.data = {id: 1, name: 'runkids'}
if(response.data === null) {
return []
}
const finalResponse = await otherAPIById(response.data.id)
return finalResponse // [{message: 'Hello', sender: 'runkids'}]
}
})
console.log(data) //[{message: 'Hello', sender: 'runkids'}]
The onFetchError
can intercept the response before data and error updated
const { data, error } = useConditionWatcher({
fetcher,
conditions,
async onFetchError({data, error}) {
if(error.code === 401) {
await doSomething()
}
return {
data: [],
error: 'Error Message'
}
}
})
console.log(data) //[]
console.log(error) //'Error Message'
More Configs
-
config
: An object of config for vue-condition-watcher-
fetcher
(⚠ï¸? Required) : Can be any asynchronous function to fetch data -
conditions
(â ï¸? Required) : An object of conditions, also be initial value -
defaultParams
: An object of fetcher's default parameters -
initialData
:data
default value is null, and you can settingdata
default value by use this config -
immediate
: Setting theimmediate
to false will prevent the request until theexecute
function called.immediate
default istrue
.const config = { fetcher: params => axios.get('url', { params }), defaultParams: { type: 'member' }, immediate: true, initialData: [] conditions: { offset: 0, limit: 10, username: '', }, }
-
-
queryOptions
: An object of options to sync query string with conditions-
â ï¸? queryOptions
work base on vue-router, you need install vue-router first. -
sync
: key of provide name ( String | Symbol )- main.js: register router
import {createApp} from 'vue' import App from './App.vue' import { router } from './router' const app = createApp(App) .provide('router', router) // it's should be required .use(router) .mount('#app')
- then
useConditionWatcher(config, {sync: 'router'})
-
ignore
: you can ignore key name from conditions, will not push with query.useConditionWatcher(config, {sync: 'router', ignore: ['offset', 'limit']})
-
navigation
: use vue router navigation method push or replace, default value is push.useConditionWatcher(config, {sync: 'router', navigation: 'replace'})
-
How to use in vue@2 with @vue/composition-api
-
( Good ) Add
provide
inmain.js
new Vue({ el: '#app', router, store, provide: { router }, render: h => h(App) })
-
Add
provide
in current fileimport { useConditionWatcher } from "vue-condition-watcher"; import { provide } from "@vue/composition-api"; import router from "@/router"; import api from "../api"; export default { setup() { provide("router", router); const config = { fetcher: api.users, conditions: { offset: 0, limit: 9 } }; return useConditionWatcher(config, {sync: 'router', ignore: ['offset', 'limit']}); } };
How to use in Nuxt with @nuxtjs/composition-api
-
Add
provide
in current fileimport { useConditionWatcher } from "vue-condition-watcher"; import { defineComponent, useRoute, provide, useContext } from "@nuxtjs/composition-api"; import api from "~/api"; export default defineComponent({ setup() { const route = useRoute(); const { app } = useContext(); provide('router', app.router); const config = { fetcher: api.users, conditions: { offset: 0, limit: 9 } }; return useConditionWatcher(config, {sync: 'router', ignore: ['offset', 'limit']}); } });
Return Values
conditions
: An object and returns a reactive proxy of conditionsdata
: Data resolved byconfig.fetcher
error
: Error thrown byconfig.fetcher
loading
: Request is fetchingexecute
: The function to fetch dataresetConditions
: Reset conditions to initial valueonConditionsChange
: Will fire on conditions changedonFetchSuccess
: Will fire on fetch request successonFetchError
: Will fire on fetch request erroronFetchFinally
: Will fire on fetch finished
Lifecycle
-
onConditionsChange
Fire new conditions value and old conditions value.
onConditionsChange((cond, preCond)=> { console.log(cond) console.log(preCond) })
-
beforeFetch
You can modify conditions before fetch, or you can call second of arguments to stop fetch this time.
const { conditions } = useConditionWatcher({ fetcher, conditions, beforeFetch }) async function beforeFetch(cond, cancel){ if(!cond.token) { // stop fetch cancel() // will fire onConditionsChange again conditions.token = await fetchToken() } return cond })
-
afterFetch
&onFetchSuccess
afterFetch
fire beforeonFetchSuccess
afterFetch
can modify data before update.Type Modify data before update afterFetch config âï¸? onFetchSuccess event â? <template> {{ data?.detail }} <!-- 'xxx' --> </template>
const { data, onFetchSuccess } = useConditionWatcher({ fetcher, conditions, async afterFetch(response){ //response = { id: 1 } const detail = await fetchDataById(response.id) return detail // { id: 1, detail: 'xxx' } }) }) onFetchSuccess((response)=> { console.log(response) // { id: 1, detail: 'xxx' } })
-
onFetchError(config)
&onFetchError(event)
config.onFetchError
fire beforeevent.onFetchError
config.onFetchError
can modify data and error before update.Type Modify data before update Modify error before update onFetchError config âï¸? âï¸? onFetchError event â? â? const { onFetchError } = useConditionWatcher({ fetcher, conditions, onFetchError(ctx){ return { data: [], error: 'Error message.' } }) }) onFetchError((error)=> { console.log(error) // origin error data })
-
onFetchFinally
Will fire on fetch finished.
onFetchFinally(async ()=> { //do something })
Make It Reusable
You might need to reuse the data in many places. It is incredibly easy to create reusable hooks of vue-condition-watcher
:
function useUserExpensesHistory (id) {
const { conditions, data, error, loading } = useConditionWatcher({
fetcher: params => api.user(id, { params }),
defaultParams: {
optExpand: 'amount,place'
},
conditions: {
daterange: []
}
immediate: false,
initialData: [],
beforeFetch(cond, cancel) {
if(!id) {
cancel()
}
const { daterange, ...baseCond } = cond
if(daterange.length) {
[baseCond.created_at_after, baseCond.created_at_before] = [
daterange[0],
daterange[1]
]
}
return cond
}
})
return {
histories: data,
isFetching: loading,
isError: error,
daterange: conditions.daterange
}
}
And use it in your components:
<template>
<el-date-picker
v-model="daterange"
:disabled="isFetching"
type="daterange"
range-separator="To"
start-placeholder="Start date"
end-placeholder="End date"
/>
<div v-for="history in histories" :key="history.id">
{{ `${history.created_at}: ${history.amount}` }}
</div>
</template>
<script setup>
const {
daterange,
histories,
isFetching,
isError
} = useUserExpensesHistory(route.params.id)
onMounted(() => {
//start first time data fetching after initial date range
daterange = [new Date(), new Date()]
})
</script>