Using debouncer with React event - javascript

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.

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

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

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} />;
};

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:
Event fires
_.debounce() sets a 500 millisecond timeout
React nullifies the event object
The timer fires and calls your event handler
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.

class HelloWorldComponent extends Component {
_handleInputSearchChange = (event) => {
event.persist();
_.debounce((event) => {
console.log(event.target.value);
}, 1000)(event);
};
render() {
return (
<input onChange={this._handleInputSearchChange} />
);
}
}

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
}

Related

Lit element, close drop down when click outside of component

I'm working with Lit Element and I'm trying add an event listener on 'Click' that will a variable state that will set the dropdown to be expand or not. But once the drop down is 'closed' I want to remove that event to avoid unnecessary event calls on 'Click.
Adding the event works great but I cannot remove it.
Here is the idea:
public willUpdate(changedProps: PropertyValues) {
super.willUpdate(changedProps);
if (changedProps.has("_tenantsExpanded")) {
document.removeEventListener("click", (ev) => this._eventLogic(ev, this));
if (this._tenantsExpanded)
document.addEventListener("click", (ev) => this._eventLogic(ev, this));
}
}
The fct logic:
private _eventLogic(e: MouseEvent, component: this) {
const targets = e.composedPath() as Element[];
if (!targets.some((target) => target.className?.includes("tenant"))) {
component._tenantsExpanded = false;
}
}
Code in my render's function:
${this._tenantsExpanded
? html` <div class="tenants-content">${this._tenantsContent()}</div> `
: html``}
Important note: I want the click event to be listened on all the window, not just the component itself. The same for removing the event.
PS: I don't know why e.currentTaget.className doesn't give me the actual className, but results to an undefined.
When you use removeEventListener you have to pass a reference to the same function you used when adding the listener.
In this example the function is stored in fn.
(You might have to change the this reference here, it depends a bit on your whole component).
const fn = (ev) => this._eventLogic(ev, this);
document.addEventListener("click", fn);
document.removeEventListener("click", fn);

How can I stop this document attached event listener from emitting duplicate events?

I'd like for a game to fire events upon key presses, but when I navigate away from and back to the game page the event listener fires twice. I found another issue suggesting I remove the listener before adding it, but that isn't helping.
The listener is added during mounted
mounted() {
this.setupInputListener();
},
And the keydown event listener is added to the document
keydownListener() {
const action = handleKeyDown(event.key);
if ( action ) {
this.SEND_INPUT({
gameId: this.gameId,
action: action
});
}
},
setupInputListener() {
document.removeEventListener('keydown', this.keydownListener);
document.addEventListener('keydown', this.keydownListener);
}
How can I prevent this keydown listener from emitting duplicate events?
You cannot add an event listener multiple times, that will cause it to fire once or many times. To avoid this, try using onkeydown. Once we do this we no longer need the removeEventListener. For example, something like this should work:
keydownListener() {
const action = handleKeyDown(event.key);
if (action) {
this.SEND_INPUT({
gameId: this.gameId,
action: action
});
}
},
setupInputListener() {
document.onkeydown = () => {
this.keydownListener;
}
}
Hoped this helped!

How to make useEffect rerun only on change of a single dependency?

I have created a custom input component that has an attached 'edit/submit' button, like so:
...........................
I want the onChange handler passed to it to fire only when user hits the submit button i.e. (✓) and not on every keypress. So, in useEffect I did:
useEffect(() => {
if (!editMode) { // only on submit
onChange(value) // "value" is a state of my component
}
}, [editMode])
return (
<div className='my-input'>
<input value={value} onChange={({target}) => setValue(target.value)}/>
<Icon
name={editMode ? 'tick' : 'pencil'}
onClick={() => setEditMode(editMode => !editMode)}
/>
</div>
)
But the linter starts to bang:
useEffect has missing dependencies: 'onChange' and 'value'...
How to fix this issue or am I using an incorrect hook here?
The warning you're seeing is because your useEffect function doesn't depend on a key press, it depends on the value of onChange and value. A better way to accomplish this might be to send the event inside of the onChange handler itself:
const onClick = React.useCallback(() => {
const nextEditMode = !editMode
setEditMode(nextEditMode)
// If we're flipping editMode on, fire the event.
if (nextEditMode) {
onChange(value)
}
}, [value, onChange])
useEffect should be used when you can only represent the thing you want as a side-effect, but there's no need to do that in this case; this can handled in event handler, and it is easier to read when you do so.

Blocking onClick handler with addEventListener

I have a button with onClick event attached to it:
<button id="my-button" onClick={myMethod}>
My button
</button>
I have also added an event listener to this button:
const listener = (e) => {
// Do something here (or elsewhere) to prevent `myMethod` from being invoked
console.log('Hello, world!');
}
const options = { capture: true, once: true };
document.getElementById('my-button')
.addEventListener('click', listener, options);
Is it possible to add some method inside the listener, so the myMethod is stopped from being invoked?
Combining React event handling and raw DOM event handling usually indicates a larger design issue. Having the one conflict with the other even more so. :-)
Having said that, React's event handlers use delegation, so the standard e.stopPropagation should do it:
const listener = (e) => {
e.stopPropagation();
console.log('Hello, world!');
};
Example:
function myMethod() {
console.log("myMethod");
}
const Example = () => <button id="my-button" onClick={myMethod}>
My button
</button>;
ReactDOM.render(
<Example />,
document.getElementById("root")
);
const listener = (e) => {
// Do something here (or elsewhere) to prevent `myMethod` from being invoked
e.stopPropagation();
console.log('Hello, world!');
}
const options = { capture: true, once: true };
document.getElementById('my-button')
.addEventListener('click', listener, options);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Note that you'll need to re-attach your event handler every time React re-renders the component. This is part of why mixing these two systems is generally not your best approach.

onClick function is running on another event listener

Have been playing around with react. Have two event listeners the input which listens onChange and the button which should push the value to the array when its clicked.
Here's the code:
import React, { Component } from 'react';
let arr = [];
class App extends Component {
constructor() {
super();
this.state = {text: 'default'}
}
update( e ) {
this.setState({text: e.target.value})
}
add ( value ) {
arr.push(value)
console.log(arr)
}
render() {
return (
<div>
<h1>{this.state.text}</h1>
<input onChange={this.update.bind(this)}/>
<button onClick={this.add(this.state.text)}>Save</button>
</div>
)
}
}
export default App
The problem that the add function is running on change. Can't really get why.
Any suggestions?
onChange() triggers update()
update() calls this.setState() which changes state.
A state change causes render() to be invoked to re-render according to new state.
Rendering <button onClick={this.add(this.state.text)}>Save</button> invokes add() every time render() runs.
In order to defer invoking add(), you can define a function which gets triggered by the click event, as was shown in another answer. Alternatively, you can achieve the same functionality by adding a class method which encapsulates the trigger functionality:
addText() {
this.add(this.state.text)
}
render() {
…
<button onClick={this.addText.bind(this)}>Save</button>
This may or may not work for you, but in the context of the example, given, this would work.
Change <button onClick={this.add(this.state.text)}>Save</button>
To <button onClick={() => this.add(this.state.text)}>Save</button>
In your variant function add firing when component is rendering, and when you call setState with onChange of input you call this re-render.
The problem is add(this.state.text) is called whenever render() is called. To avoid this, you do not need to send the state as parameter, all you need to do is
<button onClick={this.add}>Save</button
or if you want to send a parameter you should bind it
<button onClick={this.add.bind(this, this.state.text)}>Save</button>

Categories