README
English | ηΉι«δΈζ
vue-condition-watcher πΆ
Introduction
Vue composition API for automatic data fetching and easily control conditions
requires Node.js 12.0.0 or higher.
Features
β Automatic fetch data when conditions changed.
β Automatic filter falsy value in conditions before fetch.
β Automatic converts the corresponding type. (string, number, array, date)
β Store the conditions within the URL query string every time a condition is changed
β Sync the state with the query string and initialize off of that and that back/forward/execute work.
β Keep requests first in β first out.
β Dependent request before update data.
β Easily manage paged data and customized your pagination hook.
β Works for Vue 2 & 3 by the power of vue-demi
π Download Vue3 example here (Use Vite)
cd examples/vue3
yarn
yarn serve
π Download Vue2 @vue/composition-api example here
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 router = useRouter()
const { conditions, data, loading, error } = useConditionWatcher(
{
fetcher,
conditions: {
name: ''
},
history: {
sync: router
}
}
)
return { conditions, data, loading, error }
},
})
.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.
Use config.history
of sync to sync: router
. Will store the conditions within the URL query string every time conditions change.
Basic Usage
const { conditions, data, error, loading, execute, resetConditions, onConditionsChange } = useConditionWatcher(config)
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()
Manually Trigger Request
By default, vue-condition-watcher
will automatically trigger fetch data. You can pass manual
to disable the default fetch and then use execute()
to trigger fetch data.
const { execute } = useConditionWatcher({
fetcher,
conditions,
manual: true,
})
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, also your can requestss depend on each other π
const { data } = useConditionWatcher({
fetcher,
conditions,
async afterFetch(response) {
//response.data = {id: 1, name: 'runkids'}
if(response.data === null) {
return []
}
// requests depend on each other
// the loading is still be true until fire `onFetchFinally`
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'
Configs
fetcher
(β οΈRequired) : A promise returning function to fetch your dataconditions
(β οΈRequired) : An object of conditions, also to be initial valuedefaultParams
: An object of fetcher's default parametersinitialData
:data
default value is null, and you can settingdata
default value by use this configimmediate
: Setting theimmediate
to false will prevent the request until theexecute
function called.immediate
default istrue
.manual
: You can usemanual
to disabled automatically fetch datahistory
: Sync conditions value to URL query stringbeforeFetch
: You can modify conditions before fetch, or you can call second of arguments to stop fetch this time.afterFetch
: You can modify data before update. also can usemutate
modify too. But still recommend modifydata
atafterFetch
.onFetchError
: Handle error, and you can modify data and error before update here.
Return Values
conditions
(reactive
) : An object and returns a reactive proxy of conditionsdata
(πβπ¨ readonly & β οΈ ref
) : Data resolved byconfig.fetcher
error
(πβπ¨ readonly & ref
) : Error thrown byconfig.fetcher
loading
(πβπ¨ readonly & ref
) : Request is fetchingexecute
: The function to trigger the requestmutate
: You can use mutate() to directly modifydata
( By default, data is readonly )resetConditions
: 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| Dependent request | |-----|--------|------|------| |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: {
opt_expand: '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 baseCond
}
})
return {
histories: data,
isFetching: loading,
isError: error,
daterange: conditions.daterange
}
}
And use it in your components:
<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>
<template>
<el-date-picker
v-model="daterange"
:disabled="isFetching"
type="daterange"
/>
<div v-for="history in histories" :key="history.id">
{{ `${history.created_at}: ${history.amount}` }}
</div>
</template>
Congratulations! π₯³ You have learned how to use composition-api with vue-condition-watcher
.
Now we can manage the paging information use vue-condition-watcher
.
Pagination
Here is an example use Django the limit and offset functions and Element UI.
Create usePagination
function usePagination () {
let cancelFlag = false // check this to cancel fetch
const { startLoading, stopLoading } = useLoading()
const { conditions, data, execute, resetConditions, onConditionsChange, onFetchFinally } = useConditionWatcher(
{
fetcher: api.list,
conditions: {
daterange: [],
limit: 20,
offset: 0
}
immediate: true,
initialData: [],
history: {
sync: 'router',
// You can ignore the key of URL query string, prevent users from entering unreasonable numbers by themselves.
// The URL will look like ?offset=0 not show `limit`
ignore: ['limit']
},
beforeFetch
},
)
// use on pagination component
const currentPage = computed({
get: () => conditions.offset / conditions.limit + 1,
set: (page) => {
conditions.offset = (page - 1) * conditions.limit
}
})
// onConditionsChange -> beforeFetch -> onFetchFinally
onConditionsChange((newCond, oldCond) => {
// When conditions changed, reset offset to 0 and then will fire beforeEach again.
if (newCond.offset !== 0 && newCond.offset === oldCond.offset) {
cancelFlag = true
conditions.offset = 0
}
})
async function beforeFetch(cond, cancel) {
if (cancelFlag) {
// cancel fetch when cancelFlag be true
cancel()
cancelFlag = false // reset cancelFlag
return cond
}
// start loading
await nextTick()
startLoading()
const { daterange, ...baseCond } = cond
if(daterange.length) {
[baseCond.created_at_after, baseCond.created_at_before] = [
daterange[0],
daterange[1]
]
}
return baseCond
}
onFetchFinally(async () => {
await nextTick()
// stop loading
stopLoading()
window.scrollTo(0, 0)
})
return {
data,
conditions,
currentPage,
resetConditions,
refetch: execute
}
}
And use it in your components:
<script setup>
const { data, conditions, currentPage, resetConditions, refetch } = usePagination()
</script>
<template>
<el-button @click="refetch">Refetch Data</el-button>
<el-button @click="resetConditions">Reset Offset</el-button>
<el-date-picker
v-model="conditions.daterange"
type="daterange"
/>
<div v-for="info in data" :key="info.id">
{{ info }}
</div>
<el-pagination
v-model:currentPage="currentPage"
v-model:page-size="conditions.limit"
:total="data.length"
/>
</template>
When daterange or limit changed, will reset offset to 0 and only fetch data again after reset offset.
TDOD List
- Cache
- Prefetching
- Automatic Revalidation
- Pulling