Extended finite options or composability using filters (was debouncing option for useStorage)
See original GitHub issueI was looking into the debouncing option you raised for useStorage and I wanted to first see if the same can be achieved in a more flexible way.
Please take this as a dump of ideas and not a definite API. I thought about this while trying to design the API for other composables, and I want to share it in case it is useful in some way for vueuse.
The problem that I see with adding a denounce option is that we may also want to add a throttle one later (or pausable, or other ideas we may have later). We are adding complexity to the main composable, and users will need to pay for this even if they do not use it. There are other composables like useRefHistory where users may expect the same functionality.
So, what if the API lets the user define the filtering function to be used? I think you called it updater in the useDebounce implementation. I am using filter
as the option name just because it is called like that in Rx, but I am not married with the name. The signature for filter is ( (fn) => void ) => void
const count = useStorage('counter', 0, { filter: debounceFilter(200) })
const count = useStorage('counter', 0, { filter: throttleFilter(300) })
const { filter, pause, resume } = pausableFilter()
const count = useStorage('counter', 0, { filter })
debounceFilter
could be implemented as:
export function debounceFilter(delay = 200) {
return (fn) => useDebounceFn(() => fn(), delay)
}
The idea is that inside useStorage, instead of a direct watch(data, commit, options)
we use watch(data, filter(commit), options)
So, the filter
option gives users the possibility to decide which triggered values to accept from the watch.
Also, if we have these filters in the lib, we can also implement useDebounceRef, useThrotleRef, etc using a more general useFilteredRef
export function pausableFilter() {
const tracking = ref(true)
function pause() {
tracking.value = false
}
function resume() {
tracking.value = true
}
function filter(fn) {
return () => {
if( tracking.value ) {
fn()
}
}
return { tracking, pause, resume, filter }
}
The filters are composable also, allowing users to create pausable throttled storage for example
const { filter, pause, resume } = pausableFilter()
const count = useStorage('counter', 0, { filter: filter(throttleFilter(300)) })
And they can actually create their own filters, pausing or delaying according to their app needs (using other app refs, etc)
I also wonder if we could simplify useRefHistory by removing the pausable part out of the main composable, and letting users choose if they want that functionality
const data = ref(0)
const { filter, pause, resume } = pausableFilter()
const { undo, redo } = useRefHistory(data, { filter })
It is also good that we are not adding an extra watch for every filter. Another possible API for composables that are watching an input ref is to expect the user to give us a filtered ref, something like:
const data = ref(0)
const { pausableRef, pause, resume } = usePausableRef(data)
const { undo, redo } = useRefHistory(pausableRef)
This only works with API that are tracking an input ref (so we can not use it in useStorage), but we could offer this possibility also.
I think there are other possible APIs we could explore. I thought about letting the user give us the watch
function to be used, but then you may need to use the same options (flush, deep) in several places.
For common cases, we can also bundle filters for convenience like:
const data = ref(0)
const { undo, redo, pause, resume } = usePausableRefHistory(data)
implemented as
export function usePausableRefHistory(ref, options) {
const { filter, pause, resume } = pausableFilter()
return { ...useRefHistory(data, { filter }), pause, resume }
}
So we can still offer the general composable and specialized ones, and users will only pay for what they use. Take the pausable filter extraction as an example to extrapolate to other cases, because the current API allows users to commit using resume(true)
. There may be a better API to also support external triggers. Or the filter may also receive the value, so you can implement validation filters, remove values if they are negative, etc. Or that maybe another option orthogonal to this one.
What do you think? Is something like a filter option worth exploring so we do not need to implement debounce/throttle/pausable in each composable?
Disclaimer: I do not know if I can actually do this myself but looks interesting, so at least I wanted to share it 😃
Issue Analytics
- State:
- Created 3 years ago
- Comments:15 (15 by maintainers)
Top GitHub Comments
Ya, totally, I also thought about that but the clean version was too tempting 😄
Also, users can also have an utility if they would want the other interface for their filters too
like
So not a big deal 👍
Really cool to see the filters feature in the lib 🙌 That was a lot faster than I expected 🎉
I don’t know, yet.
invoke
is something that “leave as it is” and I believe in most of the cases, users would only needinvoke
to make their filters. But think about more advanced usages, you may also want to alter the args passed tofn
. I can’t think about the usage yet, but I would prefer not to limit the possibility of doing things.Yeah, I do like it, this does make the code cleaner. However, it also makes you lose the ability to extend the signature to add more arguments in the future.
I think I would prefer to keep the object destructuring style as I would treat this API as an advanced one. It’s not ideal for me to trade flexibility for “cleanness”. Hope I made myself clear :p