keydown firing multiple times when keydown (reactjs) - javascript

I am adding an event listener and checking if its level 1, but when I press the space key once, it fires 50times or more. Please help
document.addEventListener("keyup", function(e) {
if(level === 1){
if(e.code === "Space") {
console.log('space press');
click1();
}
}
});

Since this is tagged with React, given the code you have here and the issue you describe, it is almost certain that you are binding an event listener every render. Which means you are ending up with way more listeners than you want. What you need to do is use React when you are using React.
For example below, we have an input that logs on any keypress, and we also manually create an event listener. At first, when you type, you will get one log for each. However, once you click the button (triggering a rerender), you will start getting multiple "manual" events, but still the single "react" event:
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = { count: props.count };
}
inc() {
this.setState(prev => ({count: prev.count+1}));
}
render() {
document.addEventListener("keyup", function(e) {
console.log('manual space press');
});
return <div onKeyUp={(e) => {
console.log('React: space press');
}}>
<button onClick={() => this.inc()}>{this.state.count}</button>
<input />
</div>
}
}
ReactDOM.render(<Hello count={0}/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id='root'></div>

This is something called Event Bubblingwhich basically means that the event gets fired once on each parent element until it reached HTML.
you can learn about it here: https://dev.to/eladtzemach/event-capturing-and-bubbling-in-react-2ffg#:~:text=Event%20Bubbling%20and%20Capturing%20in%20React&text=Bubbling%20is%20as%20straightforward%20as,our%20example%20in%20the%20beginning.
you are able to prevent the default behavior but it's generally a good practice to leave it as is if you don't have a specific use for disabling it.
from the code snippet, I don't see why is this tagged with react but another reason for your problem is that you may be putting this code inside of your render() function or inside of any react life cycle function which is causing this snippet to run with each rerender leaving you with a punch of unwanted listeners which is not only functionality you don't want but also something that will slow down you app
overtime ie. until the user refresh the page.

useEffect(() => {
const handleEscape = (event) => {
if (event.keyCode === 27) {
console.log('Hello')
}
};
window.addEventListener('keydown', handleEscape);
return () => {
window.removeEventListener('keydown', handleEscape);
};
}, []);

Related

Event listeners not removed in React even with useEffect hook's return callback

I have a component which registers a key event during initialization.
This event triggers API call to fetch random Data.
useEffect(() => {
const keyUpEvent = (event) => {
if (event.code === "Enter") {
event.preventDefault();
fetchRandomData();
}
};
document.addEventListener("keyup", keyUpEvent);
fetchRandomData();
return () => {
setChord({});
document.removeEventListener("Keyup", keyUpEvent);
};
}, []);
But even with the above code, the event is not removed when I navigate between pages and trigger the event by key strokes ( Enter KEY ).
Based on the below image, this happens when I press the key once after multiple page navigations. ( MEMORY LEAK )
PROBLEM :
Why is react not removing the registered event? What is the proper way to remove event listeners..?
You are capitalizing incorrectly. You just change Keyup in removeEventListener to keyup.

e.stopPropagation / e.nativeEvent.stopImmediatePropagation not working in react

I have had this working before so I'm not sure if I'm making a silly mistake or it is somewhere else in my code.
Here is the simple component I am testing:
import React, { Component } from 'react'
class TestPropagation extends Component {
handleBodyClick = () => {
console.log(`Did not stop propagation`)
}
componentDidMount() {
const body = document.querySelector(`body`)
body.addEventListener(`click`, this.handleBodyClick)
}
componentWillUnmount() {
const body = document.querySelector(`body`)
body.removeEventListener(`click`, this.handleBodyClick)
}
render() {
return (
<div
style={{
position: `absolute`,
top: `200px`,
left: `20%`,
width: `calc(80vw - 20%)`,
height: `500px`,
color: `white`,
background: `green`,
cursor: `pointer`,
}}
onClick={e => {
e.stopPropagation()
e.nativeEvent.stopImmediatePropagation()
console.log(`Clicked Div Handler`)
}}
>
Test Propagation Component
</div>
)
}
}
export default TestPropagation
If I am correct that should prevent the console log of Did not stop propagation from happening when I click the div, but it is not.
It's actually really interesting! Seems that the addEventListener precedes the onClick.
I managed to solve it by adding the same click listener to the test element, which worked as expected (stopped the click propagation to the body):
componentDidMount() {
const body = document.querySelector('body');
body.addEventListener('click', this.handleBodyClick);
// This is me adding the click listener the same way you did
document.getElementById('my_element').addEventListener('click', e => {
e.stopPropagation();
console.log('Clicked Div Handler 1');
})
}
I hope this isn't considered a work-around, I'm still trying to understand this behaviour better.
EDIT: I found this question, which is basically the same (only without the React setting), but has no solution that achieves what you were asking.
The click event on the body is triggered before any react synthetic event. You will need to add a condition in your function like this:
handleBodyClick = e => {
if (!e.target.className.includes('clickable')) {
console.log('Did stop propagation');
}
};
Then just add the class clickable on your component

React onClick and onTouchStart fired simultaneously

I have a div in my React app and I need to handle both clicks and touches. However, when I tap on a mobile, it fires both events.
If I swipe on a mobile or if I click on a normal browser, it works fine, only one event is fired in each case.
How can I handle this tap issue to not fire both events?
<div
className={myClasses}
onClick={this.myHandle}
onTouchStart={this.myHandle}
>
</div>
There is a defined order of when the events get fired (source):
touchstart
Zero or more touchmove events, depending on movement of the finger(s)
touchend
mousemove
mousedown
mouseup
click
If you want to prevent a click event if a touch event is fired before, you can you can use event.preventDefault() in the touchend event handler to prevent the click event from firing.
function App() {
const handleClick = () => {
alert('click');
};
const handleTouchEnd = (event) => {
alert('touchend');
event.preventDefault();
};
return (
<button
type="button"
onClick={handleClick}
onTouchEnd={handleTouchEnd}
>
Click Me
</button>
);
}
However, this is not universally applicable. For example, you cannot prevent a click event by using a event.preventDefault() in a mousedown event. In case you are looking for a solution to this (not sure when this use case applies though), you would have to use a ref instead:
function App() {
const prevent = React.useRef(false);
const handleClick = () => {
if (!prevent.current) {
alert('click');
} else {
prevent.current = false;
}
};
const handleMouseDown = () => {
prevent.current = true;
alert('mousedown');
};
return (
<button
type="button"
onClick={handleClick}
onMouseDown={handleMouseDown}
>
Click Me
</button>
);
}
Solved this problem using similar events between touch and mouse. touchStart/mouseDown or touchEnd/mouseUp. It fires one or another, according to each situation.
<div
className={myClasses}
onMouseUp={this.myHandle}
onTouchEnd={this.myHandle}
>
</div>
To avoid onClick() on touch devices you should check if the page is opened in touch devices or not.
To check weather it is opened in touch devices or not:
if (typeof document !== 'undefined') {
var isTouch = 'ontouchstart' in document.documentElement;
}
Then modify {isTouch ? undefined : this.myHandle} to your mouse event:
<div
className={myClasses}
onClick={isTouch ? undefined : this.myHandle}
onTouchStart={this.myHandle}
>
</div>
This is a few years late but found a solution that was really easy to implement. Looks like this:
import ReactDom from 'react-dom';
export default class YourClass extends Component {
componentDidMount(){
ReactDOM.findDOMNode(this).addEventListener('touchstart', (e)=>{
e.preventDefault();
console.log("touchstart triggered");
});
}
}
Seems like it intercepts and stops all onClick calls on mobile which is exactly what I was looking for

Fire event listener on element not rendered in React

I have added an external script to my index.html that renders a widget inside my react component (it will render the widget where ever I wanted to. It's not a reacting component.). I'm trying to render the widget inside the id renderDivHere.
handleClick() {
console.log("clicked on widget button")
}
render () {
<div>
......
......
<div id="renderDivHere" /> // the external scripts will render the widget here
......
</div>
}
The widget contains some graphs, data and a button with id="wl-nav-button". I want to listen the click events in #wl-nav-button and call this.handleClick() function.
I tried jquery.click() and document.getElementById(wl-nav-button).addEventListener("click", function(){this.handleClick()});. But both are returning errors.
Help me out on this issue.
Try with the following code:
componentDidMount() {
document.addEventListener("click", this.navButtonListener);
}
componentWillUnmount() {
document.removeEventListener("click", this.navButtonListener);
}
navButtonListener = (e) => {
if(e.target.id == "wl-nav-button"){
this.handleClick();
}
}
What this does is it adds an event listener to the entire document, which is fired anytime you click. Then, a check is done to see what the clicked items id attribute is set to. If the id is your button, it fires the handleClick() function.
This also removes the event listener from your component before un-mounting it.
Demo
Here is a simple demo that just demonstrates how to add an event handler in React to something that was not rendered with React.
class MyApp extends React.Component {
componentDidMount() {
document.addEventListener("click", this.navButtonListener);
}
componentWillUnmount() {
document.removeEventListener("click", this.navButtonListener);
}
navButtonListener = (e) => {
if (e.target.id == "wl-nav-button") {
this.handleClick();
}
}
handleClick() {
console.log("click!!");
}
render() {
return (
<div>
<p>A button from a simulated external non-react library will be inserted asynchronically below:</p>
<div id="my-container"></div>
</div>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("myApp"));
//simulate a non-react library asynchronically inserting into the DOM:
setTimeout(function() {
var button = document.createElement("BUTTON");
var text = document.createTextNode("Click me!");
button.appendChild(text);
button.setAttribute("id", "wl-nav-button");
document.getElementById("my-container").appendChild(button);
}, 2000);
<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>
<div id="myApp"></div>

Simulate click event on react element

The bounty expires in 7 days. Answers to this question are eligible for a +50 reputation bounty.
ajaykools wants to reward an existing answer:
Worth bounty, only way simulate clicks on dynamic elements like svg, g, circle, etc which are generated on page load.
I'm trying to simulate a .click() event on a React element but I can't figure out why it is not working (It's not reacting when I'm firing the event).
I would like to post a Facebook comment using only JavaScript but I'm stuck at the first step (do a .click() on div[class="UFIInputContainer"] element).
My code is:
document.querySelector('div[class="UFIInputContainer"]').click();
And here's the URL where I'm trying to do it: https://www.facebook.com/plugins/feedback.php...
P.S. I'm not experienced with React and I don't know really if this is technically possible. It's possible?
EDIT: I'm trying to do this from Chrome DevTools Console.
React tracks the mousedown and mouseup events for detecting mouse clicks, instead of the click event like most everything else. So instead of calling the click method directly or dispatching the click event, you have to dispatch the down and up events. For good measure I'm also sending the click event but I think that's unnecessary for React:
const mouseClickEvents = ['mousedown', 'click', 'mouseup'];
function simulateMouseClick(element){
mouseClickEvents.forEach(mouseEventType =>
element.dispatchEvent(
new MouseEvent(mouseEventType, {
view: window,
bubbles: true,
cancelable: true,
buttons: 1
})
)
);
}
var element = document.querySelector('div[class="UFIInputContainer"]');
simulateMouseClick(element);
This answer was inspired by Selenium Webdriver code.
With react 16.8 I would do it like this :
const Example = () => {
const inputRef = React.useRef(null)
return (
<div ref={inputRef} onClick={()=> console.log('clicked')}>
hello
</div>
)
}
And simply call
inputRef.current.click()
Use refs to get the element in the callback function and trigger a click using click() function.
class Example extends React.Component{
simulateClick(e) {
e.click()
}
render(){
return <div className="UFIInputContainer"
ref={this.simulateClick} onClick={()=> console.log('clicked')}>
hello
</div>
}
}
ReactDOM.render(<Example/>, document.getElementById('app'))
<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>
<div id="app"></div>
If you don't define a class in your component, and instead you only declare:
function App() { ... }
In this case you only need to set up the useRef hook and use it to point/refer to any html element and then use the reference to trigger regular dom-events.
import React, { useRef } from 'react';
function App() {
const inputNameRef = useRef()
const buttonNameRef = useRef()
function handleKeyDown(event) {
// This function runs when typing within the input text,
// but will advance as desired only when Enter is pressed
if (event.key === 'Enter') {
// Here's exactly how you reference the button and trigger click() event,
// using ref "buttonNameRef", even manipulate innerHTML attribute
// (see the use of "current" property)
buttonNameRef.current.click()
buttonNameRef.current.innerHTML = ">>> I was forced to click!!"
}
}
function handleButtonClick() {
console.log('button click event triggered')
}
return (
<div>
<input ref={inputNameRef} type="text" onKeyDown={handleKeyDown} autoFocus />
<button ref={buttonNameRef} onClick={handleButtonClick}>
Click me</button>
</div>
)
}
export default App;
A slight adjustment to #carlin.scott's great answer which simulates a mousedown, mouseup and click, just as happens during a real mouse click (otherwise React doesn't detect it).
This answer adds a slight pause between the mousedown and mouseup events for extra realism, and puts the events in the correct order (click fires last). The pause makes it asynchronous, which may be undesirable (hence why I didn't just suggest an edit to #carlin.scott's answer).
async function simulateMouseClick(el) {
let opts = {view: window, bubbles: true, cancelable: true, buttons: 1};
el.dispatchEvent(new MouseEvent("mousedown", opts));
await new Promise(r => setTimeout(r, 50));
el.dispatchEvent(new MouseEvent("mouseup", opts));
el.dispatchEvent(new MouseEvent("click", opts));
}
Usage example:
let btn = document.querySelector("div[aria-label=start]");
await simulateMouseClick(btn);
console.log("The button has been clicked.");
Note that it may require page focus to work, so executing in console might not work unless you open the Rendering tab of Chrome DevTools and check the box to "emulate page focus while DevTools is open".
Inspired from previous solution and using some javascript code injection it is also possibile to first inject React into the page, and then to fire a click event on that page elements.
let injc=(src,cbk) => { let script = document.createElement('script');script.src = src;document.getElementsByTagName('head')[0].appendChild(script);script.onload=()=>cbk() }
injc("https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js",() => injc("https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js",() => {
class ReactInjected extends React.Component{
simulateClick(e) {
e.click()
}
render(){
return <div className="UFIInputContainer"
ref={this.simulateClick} onClick={()=> console.log('click injection')}>
hello
</div>
}
}
ReactDOM.render(<ReactInjected/>, document.getElementById('app'))
} ))
<div id="app"></div>
Kind of a dirty hack, but this one works well for me whereas previous suggestions from this post have failed. You'd have to find the element that has the onClick defined on it in the source code (I had to run the website on mobile mode for that). That element would have a __reactEventHandlerXXXXXXX prop allowing you to access the react events.
let elem = document.querySelector('YOUR SELECTOR');
//Grab mouseEvent by firing "click" which wouldn't work, but will give the event
let event;
likeBtn.onclick = e => {
event = Object.assign({}, e);
event.isTrusted = true; //This is key - React will terminate the event if !isTrusted
};
elem.click();
setTimeout(() => {
for (key in elem) {
if (key.startsWith("__reactEventHandlers")) {
elem[key].onClick(event);
}
}
}, 1000);
Using React useRef Hooks you can trigger a click event on any button like this:
export default const () => {
// Defining the ref constant variable
const inputRef = React.useRef(null);
// example use
const keyboardEvent = () => {
inputRef.current.handleClick(); //Trigger click
}
// registering the ref
return (
<div ref={inputRef} onClick={()=> console.log('clicked')}>
hello
</div>
)
}
This answer was inspired by carlin.scott code.
However, it works only with focusin event in my case.
const element = document.querySelector('element')
const events = ['mousedown', 'focusin']
events.forEach(eventType =>
element.dispatchEvent(
new MouseEvent(eventType, { bubbles: true })
)
)

Categories

Resources