8

I have an onchange event for a field that needs to be debounced, I'm using underscore for that, however when I use the debouncer the event that is passed to the React handler appears to be out of date.

<div className='input-field'>
  <input onChange={_.debounce(this.uriChangeHandler.bind(this), 500)} id='source_uri' type='text' name='source_uri' autofocus required />
  <label htmlFor='source_uri'>Website Link</label>
</div>

uriChangeHandler(event) {
    event.preventDefault();
    let uriField = $(event.target);
    let uri = uriField.val();
    this.setState({
        itemCreateError: null,
        loading: true
    });
    this.loadUriMetaData(uri, uriField);
}

I'm getting this error:

Warning: This synthetic event is reused for performance reasons. If you're seeing this, you're calling preventDefault on a released/nullified synthetic event. This is a no-op. See https‍://fb‍.me/react-event-pooling for more information.

Using the onchange without the debouncer works fine.

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
rcjsdev
  • 813
  • 2
  • 9
  • 20

6 Answers6

17

I ended up with a solution I saw on github which worked well for me. Basically you wrap the debounce function in a custom function debounceEventHandler which will persist the event before returning the debounced function.

function debounceEventHandler(...args) {
  const debounced = _.debounce(...args)
  return function(e) {
    e.persist()
    return debounced(e)
  }
}

<Input onChange={debounceEventHandler(this.handleInputChange, 150)}/>

This got rid of the synthetic event warning

xiao
  • 1,718
  • 4
  • 23
  • 31
3

in yout case it might help

class HelloWorldComponent extends React.Component {
  uriChangeHandler(target) {
    console.log(target)
  }

  render() {
    var myHandler = _.flowRight(
      _.debounce(this.uriChangeHandler.bind(this), 5e2),
      _.property('target')
    );
    return (      
      <input onChange={myHandler}  />
    );
  }
}

React.render(
  <HelloWorldComponent/>,
  document.getElementById('react_example')
);

JSBin

Also you can use _.clone instead of _.property('target') if you want to get the complete event object.

EDITED

To prevent React nullifies the event you must call event.persist() as stated on React doc:

If you want to access the event properties in an asynchronous way, you should call event.persist() on the event, which will remove the synthetic event from the pool and allow references to the event to be retained by user code.

And hence you could use e => e.persist() || e instead of _.clone JSBin

Alex Antonov
  • 14,134
  • 7
  • 65
  • 142
ZigGreen
  • 168
  • 1
  • 9
  • That did the trick thanks! Could you explain how? I used the `_.clone` bit instead of `_.property` – rcjsdev Feb 16 '16 at 17:17
2

I went with a combination of xiaolin's answer and useMemo:

const MyComponent = () => {
  const handleChange = useMemo(() => {
    const debounced = _.debounce(e => console.log(e.target.value), 1000);
    return e => {
      e.persist();
      return debounced(e);
    };
  }, []);
  return <input onChange={handleChange} />;
};
Nelu
  • 16,644
  • 10
  • 80
  • 88
0

What I think is happening is that the event is being nullified in the time in between the actual event and when your method gets called. Looking at the _.debounce source code (and using what we know about debouncing functions) will tell you that your method isn't called until 500 milliseconds until after the event fires. So you've got something like this going on:

  1. Event fires
  2. _.debounce() sets a 500 millisecond timeout
  3. React nullifies the event object
  4. The timer fires and calls your event handler
  5. You call event.stopPropagation() on a nullified event.

I think you have two possible solutions: call event.stopPropagation() every time the event fires (outside of the debounce), or don't call it at all.

Side note: this would still be a problem even with native events. By the time your handler actually gets called, the event would have already propagated. React is just doing a better job at warning you that you've done something weird.

GJK
  • 37,023
  • 8
  • 55
  • 74
0
class HelloWorldComponent extends Component {
    _handleInputSearchChange = (event) => {
        event.persist();
        _.debounce((event) => {
            console.log(event.target.value);
        }, 1000)(event);
    };

    render() {
        return (
            <input onChange={this._handleInputSearchChange}  />
        );
    }
}
Nathan Tuggy
  • 2,237
  • 27
  • 30
  • 38
0

The idea here is that we want the onChange handler to persist the event first then immediately debounce our event handler, this can be simply achieved with the following code:

<input
onChange={_.flowRight(
  _.debounce(this.handleOnChange.bind(this), 300),
  this.persistEvent,
)}
</input>

persistEvent = e => {
  e.persist();
  e.preventDefault();
  return e;
};

handleOnChange = e => {
  console.log('event target', e.target);
  console.log('state', this.state);
  // here you can add you handler code 
}
alaahd
  • 274
  • 4
  • 7