Recompose: withHandlers with debounce

Created on 31 May 2018  Â·  11Comments  Â·  Source: acdlite/recompose

How can I create a function with recompose that won't be recreated whenever the component re-renders?

I basically want to have a handler that is debounced so it is just created once, but when I do this:

withHandlers({
  debouncedFunction: props => debounce(arg => somethingToDo, 250),
}).

I can see it being created more than once every time I set the state. Is there any way to create a function that's outside of the render lifecycle so I can properly use debounce?

Most helpful comment

use functional form of withHandlers ie

withHandlers(() => { 
const blabla = debounce(blublu);
return { 
  handlersAsUsual
}

now u can use blabla inside handlers

All 11 comments

use functional form of withHandlers ie

withHandlers(() => { 
const blabla = debounce(blublu);
return { 
  handlersAsUsual
}

now u can use blabla inside handlers

@istarkov thanks, I will give this a try!

@istarkov I might be missing something, but here is what I have tried, and seems like the debounce is not working as I would expect it to work. Here is what I have so far:

withHandlers(() => {
    const sayTest = props => debounce(_query => console.log("Debouncing"), 1000);

    return {
      sayTest,
    }
  }),
  withHandlers({
    onSearchChange: props => (event: SyntheticEvent<>) => {
      if (event.target instanceof HTMLInputElement) {
        props.sayTest('something');
      }
    },

Every time onSearchChange is called and I end up calling sayTest I would expect that one to be debounced, but I end up saying several 'TEST' in console. It seems it's ignoring the 1000 rate limit of firing calls.

Anything you think I'm missing here?

just do exactly how I wrote debounce function, call it from handler. Its just javascript ;-) nothing hard.

return { handler: () => () => call blbla here

Thanks @istarkov !

@istarkov I'm trying to understand (reading the source code) why when using the functional version the debounce works as expected, whereas when I do it like this:

withHandlers({
  debouncedFunction: props => debounce(arg => somethingToDo, 250),
}).
withhandlers({
  useDebounced: props => props.debouncedFunction('foo')
})

The debounce doesn't seem to be forcing the rate limiting. What's the reason for that behavior? Isn't in this example I just wrote the debouncedFunction just be created once? That's the advantage of usingwithHandlers instead that withProps?

It will be easy to get if you'll do 2 things

1) write your own debounce implementation using setTimeout - few code lines
2) rewrite handler using function body and return statement ie => { return blabla}

try to do both and if youll have a questions after Ill write explanation

also to be clear its not recompose behaviour its how js works.

Sounds good! Thanks @istarkov

@istarkov I'm going to share the experiments I did, and at the end I will ask my questions if you don't mind. First, I made this debounce function:

function myDebounce(func, wait) {
  var timeout;
  console.log('initialize');

  return function() {
    var context = this,
      args = arguments;

    var later = function() {
      timeout = null;
      func.apply(context, args);
    }
    console.log('timeout', timeout);

    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  }
}

And then I wanted to use it in two ways. First way is by just passing the debounced function as a handler, and using it in another handler, like this:

withHandlers({
    debouncedSearch: props => myDebounce((search) => {
      console.log("Debouncing", search)
    }, 250)
  }),
withHandlers({
    onSearchChange: props => (event) => {
        const search = event.target.value;
        props.seatSearch(search) // Sets search state
        props.debouncedSearch(search);
      }
    },
  }),

If I type ab I see the following in the console:

timeout undefined
initialize
timeout undefined
called
Debouncing a
called
Debouncing ab

So, the debouncing is not working here, and I believe is because the debounced function is initialized twice.

On the second version, I did this:

withHandlers(() => {
 const debouncedSearch = myDebounce((search) => {
      console.log("Debouncing", search)
 }, 250);

 return {
   onSearchChange: props => (event) => {
        const search = event.target.value;
        props.seatSearch(search) // Sets search state
        debouncedSearch(search);
      }
    },
 }
})

Before typing ab I see initialize in the console. And then when I type ab, I see:

timeout undefined
timeout 24
called
Debouncing ab

So, what I see is that in the first version, the myDebounce function is initialized more than once (once for every character on the search), so it's impossible that it does debouncing because there are different instances. In the second version, we see the myDebounce function being initialized just once even before we start typing on the search input, and that's why the debouncing works.

I fail, however, what part of JS is making it work this way. Why when we use withHandlers({ ... }) with no functional approach it initializes the myDebounce more than once, and when we do it in a functional manner withHandlers(() => { ... }) it doesn't?

Thanks @istarkov, I really appreciate your time on this!

Why when we use withHandlers({ ... }) with no functional approach it initializes the myDebounce more than once

The answer is in js, and in

  1. rewrite handler using function body and return statement ie => { return blabla}

so this

withHandlers({
    debouncedSearch: props => myDebounce((search) => {
      console.log("Debouncing", search)
    }, 250)
  }),

become this

withHandlers({
    debouncedSearch: props => {
     const myFn = myDebounce((search) => {
        console.log("Debouncing", search)
     }, 250)
    return myFn;
   }    
  }),

latter is the same as first just written without sugar.
As you see every time you call debouncedSearch(props)(search) js executes
fn = debouncedSearch(props) then fn(search).
and on every call fn = debouncedSearch(props) you reinitialise debounce.

In functional approach, function inside withHandlers(() => ) called once on initialisation, so debounce inside will be called just once so will work as expected.

Thanks @istarkov . Really helpful

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yellowfrogCN picture yellowfrogCN  Â·  3Comments

rockchalkwushock picture rockchalkwushock  Â·  3Comments

isubasti picture isubasti  Â·  3Comments

Secretmapper picture Secretmapper  Â·  3Comments

franklinkim picture franklinkim  Â·  3Comments