Mike Rifgin
Mike Rifgin

Reputation: 10745

Throttling dispatch in redux producing strange behaviour

I have this class:

export default class Search extends Component {

    throttle(fn, threshhold, scope) {
       var last,
           deferTimer;
       return function () {
            var context = scope || this;
            var now = +new Date,
           args = arguments;
           if (last && now < last + threshhold) {
            // hold on to it
               clearTimeout(deferTimer);
               deferTimer = setTimeout(function () {
                   last = now;
                   fn.apply(context, args);
               }, threshhold);
           } else {
               last = now;
               fn.apply(context, args);
           }
       }
    }

    render() {
        return (
          <div>
            <input type='text' ref='input' onChange={this.throttle(this.handleSearch,3000,this)} />
          </div>
        )
    }

    handleSearch(e) {
        let  text = this.refs.input.value;
        this.someFunc();
        //this.props.onSearch(text)
    } 

    someFunc() {
        console.log('hi')
    }
}

All this code does it log out hi every 3 seconds - the throttle call wrapping the handleSearch method takes care of this

As soon as I uncomment this line:

this.props.onSearch(text)

the throttle methods stops having an effect and the console just logs out hi every time the key is hit without a pause and also the oSearch function is invoked.

This onSearch method is a prop method passed down from the main app:

<Search onSearch={ text => dispatch(search(text)) } /> 

the redux dispatch fires off a redux search action which looks like so:

export function searchPerformed(search) {
    return {
        type: SEARCH_PERFORMED
    }
}

I have no idea why this is happening - I'm guessing it's something to do with redux because the issue occurs when handleSearch is calling onSearch, which in turn fires a redux dispatch in the parent component.

Upvotes: 1

Views: 2272

Answers (3)

johncip
johncip

Reputation: 2259

As Mike noted, just not updating the component can you get the right behavior, if the component doesn't need updating.

In my case, I had a component that needed to poll a server for updates every couple of seconds, until some state-derived prop changed value (e.g. 'pending' vs 'complete').

Every time the new data came in, the component re-rendered, and called the action creator again, and throttling the action creator didn't work.

I was able to solve simply by handing the relevant action creator to setInterval on component mount. Yes, it's a side effect happening on render, but it's easy to reason about, and the actual state changes still go through the dispatcher.

If you want to keep it pure, or your use case is more complicated, check out https://github.com/pirosikick/redux-throttle-actions.

Upvotes: 1

Mike Rifgin
Mike Rifgin

Reputation: 10745

Thanks to luanped who helped me realise the issue here. With that understood I was able to find a simple solution. The search component does not need to update as the input is an uncontrolled component. To stop the cyclical issue I was having I've used shouldComponentUpdate to prevent it from ever re-rendering:

constructor() {
   super();
   this.handleSearch = _.throttle(this.handleSearch,1000);
}

shouldComponentUpdate() {
    return false;
}

I also moved the throttle in to the constructor so there can only ever be once instance of the throttle.

I think this is a good solution, however I am only just starting to learn react so if anyone can point out a problem with this approach it would be welcomed.

Upvotes: 0

luanped
luanped

Reputation: 3198

The problem is that the first time it executes, it goes to the else, which calls the dispatch function. The reducer probably immediately update some state, and causes a rerender; the re-render causes the input to be created again, with a new 'throttle closure' which again has null 'last' and 'deferTimer' -> going to the else every single time, hence updating immediately.

Upvotes: 2

Related Questions