question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Extended finite options or composability using filters (was debouncing option for useStorage)

See original GitHub issue

I 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:closed
  • Created 3 years ago
  • Comments:15 (15 by maintainers)

github_iconTop GitHub Comments

1reaction
patak-devcommented, Nov 9, 2020

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

toFilter( (invoke,event) =>{
  if( event.x > 0 ) {
    invoke()
  }
}

like

function toFilter( fn ) {
  return (invoke,{ args }) => fn(invoke,...args)
}

So not a big deal 👍

Really cool to see the filters feature in the lib 🙌 That was a lot faster than I expected 🎉

0reactions
antfucommented, Nov 9, 2020

About passing the fn, do you see a use case there?

I don’t know, yet. invoke is something that “leave as it is” and I believe in most of the cases, users would only need invoke to make their filters. But think about more advanced usages, you may also want to alter the args passed to fn. I can’t think about the usage yet, but I would prefer not to limit the possibility of doing things.

cosnt myArgs = args.slice(0, 1)

fn.apply(thisArgs, myArgs)

spread the args

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

Read more comments on GitHub >

github_iconTop Results From Across the Web

Add debouce time to build-in filters of datagrid #5972 - GitHub
Have a debounce option in the build-in filters of datagrid. ... If we are using server side pagination, this means multiple calls to...
Read more >
Add debounce option to AJAX filter fields [#3143421] - Drupal
I had to use AJAX submit on text filter, and UX is not very friendly as user types in a value and AJAX...
Read more >
Debouncing using Binary Finite Impulse Reponse Filter
The filtered array can be ad hoc, created by hand using a priori knowledge, or generated by a program that computes the ideal...
Read more >
Cloud Connectivity and Embedded Sensory Systems
Embedded systems not only interact with their environment, but are nowadays also equipped with communication interfaces. For a long time, this digital commu ......
Read more >
crates.io/crates - SJTUG Mirror Index
actix-ip-filter/ · actix-irc/ · actix-json-responder/ ... algebraic-equation-over-finite-prime-field/ ... async-graphql-extension-apollo-tracing/.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found