Inner button onClick event not being fired with React - javascript

Hi I have something like this in a React application:
<li onClick={this.props.onToggleTodo}>
Todo
<button onClick={this.props.onRemove}>Remove</button>
<li>
But when I click on remove button... the handler onToggleTodo is fired but onRemove isn't.
I fixed by doing this on handler:
onToggleTodo = t => (e) => {
if(e.target.tagName == 'LI'){
this.setState({done: true});
}
}
It wasn't firing because of setState because it makes React render again...
That is NOT the best way, RIGHT?????
Here is the demo and the code

It's probably because the onToggleTodo is on the li and the li is a container for the button... You might want to try working as following:
<li>
<span onClick={this.props.onToggleTodo}>Todo</span>
<button onClick={this.props.onRemove}>Remove</button>
<li>
edit
If you REALLY want to work the way you were doing at first, I think this should work also:
<li {todoToggled ? onClick={this.props.onToggleTodo}}>
Todo
<button onClick={this.props.onRemove}>Remove</button>
<li>
onToggleTodo = () => {
this.setState({
todoToggled = true
})
//Rest of your code
}
onRemove = () => {
this.setState({
todoToggled = false
})
//Rest of your code
}
I'm not 100% if the syntax is correct here because I'm writing this without any feedback, but I think it's headed in the correct direction.

its javascript.. so..
onToggleTodo = t => (e) => {
this.setState({done: true});
}
onRemove = t => (e) => {
e.stopPropagation();
console.log(e.target);
}

Related

Directives for Detecting Clicks outside Element

Based on this Article https://medium.com/#Taha_Shashtari/an-easy-way-to-detect-clicks-outside-an-element-in-vue-1b51d43ff634
i implemented the same methodology of the directive for detecting outside element click, at first i had to change things as vue 2 directives have been changed in vue 3, but i got so far that:
When i click the Icon to Toggle the Box -> The box is shown
When i click outside the Box -> The box is toggled
The only thing that isn't working is when i click inside the box itself it gets toggled again, which isnt suppose to happen.
Code
Directive:
let handleOutsideClick;
const closable = {
beforeMount(el, binding, vnode) {
handleOutsideClick = (e) => {
e.stopPropagation();
const { handler, exclude } = binding.value;
let clickedOnExcludedEl = false;
exclude.forEach((id) => {
if (!clickedOnExcludedEl) {
const excludedEl = document.getElementById(id);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
binding.instance[handler]();
}
};
document.addEventListener("click", handleOutsideClick);
document.addEventListener("touchstart", handleOutsideClick);
},
afterMount() {
document.removeEventListener("click", handleOutsideClick);
document.removeEventListener("touchstart", handleOutsideClick);
},
};
export default closable;
PS: I changed the usage of refs into IDs
CartIcon:
<template>
<div
id="checkoutBoxHandler"
ref="checkoutBoxHandler"
#click="showPopup = !showPopup"
class="cart-icon"
>
<font-awesome-icon icon="fa-solid fa-cart-shopping" />
<span id="cart-summary-item">{{ cartItemsCount }}</span>
<div
v-show="showPopup"
v-closable='{
exclude: ["checkoutBox","checkoutBoxHandler"],
handler: "onClose",
}'
id="checkoutBox"
>
<CheckOutBox v-if="this.userCart" :userCart="this.userCart"></CheckOutBox>
</div>
</div>
</template>
onClose handler:
onClose() {
this.showPopup = false;
},
Can anyone see what i might be doing wrong here or maybe missing?
Thanks in advance
EDIT after Turtle Answers:
This is the Code i m using:
Directive:
const clickedOutsideDirective = {
mounted(element, binding) {
const clickEventHandler = (event) => {
event.stopPropagation();
console.log(element.contains(event.target))//True on click on the box
if (!element.contains(event.target)) {
binding.value(event)
}
}
element.__clickedOutsideHandler__ = clickEventHandler
document.addEventListener("click", clickEventHandler)
},
unmounted(element) {
document.removeEventListener("click", element.__clickedOutsideHandler__)
},
}
export default clickedOutsideDirective
Component:
<div
id="checkoutBoxHandler"
ref="checkoutBoxHandler"
#click="showPopup = !showPopup"
v-closable='onClose'
class="cart-icon"
>
<font-awesome-icon icon="fa-solid fa-cart-shopping" />
<span id="cart-summary-item">{{ cartItemsCount }}</span>
<div
v-show="showPopup"
ref="checkoutBox"
id="checkoutBox"
>
<CheckOutBox :userCart="this.userCart"></CheckOutBox>
</div>
</div>
The box is being displayed but on click on the box it still disappear
It looks like the problem could be multiple registered event listeners.
afterMount should be unmounted. If fixing that isn't enough, you may need to ensure you're unregistering the event correctly. You can store the handler on the element like this:
const closable = {
beforeMount(el, binding, vnode) {
el.__handleOutsideClick__ = (e) => {
e.stopPropagation();
const { handler, exclude } = binding.value;
let clickedOnExcludedEl = false;
exclude.forEach((id) => {
if (!clickedOnExcludedEl) {
const excludedEl = document.getElementById(id);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
binding.instance[handler]();
}
};
document.addEventListener("click", el.__handleOutsideClick__);
document.addEventListener("touchstart", el.__handleOutsideClick__);
},
// The correct lifecycle method is 'unmounted'
unmounted(el) {
document.removeEventListener("click", el.__handleOutsideClick__);
document.removeEventListener("touchstart", el.__handleOutsideClick__);
},
};
export default closable;
Other advice
Don't call stopPropagation on the event, because it could swallow clicks on other UI elements.
Forward the event when invoking the handler so that the handler can inspect it.
To ensure your directive doesn't break, you probably don't want to reference the excluded nodes by ID, but rather by ref as in the article you linked.
Or, drop the exclusions feature altogether. Without it, your directive can look like below. It looks like you're only using it to exclude things that are already inside your popup. In my experience, clicked outside should mean clicked outside. If there are additional considerations, I would prefer to let the handler take care of them by inspecting the returned event.
import { Directive } from 'vue'
// Trigger a function when a click is registered outside the element
const clickedOutsideDirective = {
mounted(element, binding) {
const clickEventHandler = (event) => {
if (!element.contains(event.target)) {
binding.value(event)
}
}
element.__clickedOutsideHandler__ = clickEventHandler
document.addEventListener("click", clickEventHandler)
},
unmounted(element) {
document.removeEventListener("click", element.__clickedOutsideHandler__)
},
}
export default clickedOutsideDirective
Now the usage looks like this
<template>
<div
id="checkoutBoxHandler"
ref="checkoutBoxHandler"
#click="showPopup = !showPopup"
class="cart-icon"
>
<font-awesome-icon icon="fa-solid fa-cart-shopping" />
<span id="cart-summary-item">{{ cartItemsCount }}</span>
<div
v-show="showPopup"
v-clicked-outside='onClose'
id="checkoutBox"
>
<CheckOutBox v-if="this.userCart" :userCart="this.userCart"></CheckOutBox>
</div>
</div>
</template>
For me the best solution for this problem is to create some object in the background.
position:fixed;
top:0;
left:0;
width: 100vw;
height: 100vh;
z-index: check which value fits you here.
So at beginning before showing "box" that object do not exist. On box show, you also show that object which is in background, above all elements except your "box".
So only thing you can click outside of your "box" is that object. And you can put event on "that object click".
And on box hide, you also hide that object;

React: how to have a Link with buttons inside, but make buttons not fire the link onclick

I have this code:
const history = useNavigate();
const navigate = (e) => {
e.preventDefault();
history('/link')
}
const doSomething = (e) => {
e.stopPropagation();
someFunc();
}
return (
<a href="/link" onClick={navigate}>
<span>Some stuff</span>
<button onClick={doSomething}></button>
</a>
);
This doesn't work because the preventDefault does not work because of the stopPropagation(). Of course I could use a div to avoid the link, or just remove the href all together but I'm striving for quality code, so what's the best option? I also can't use a button instead of a because there would be a button inside a button.

How to remove the class of an element in javascript?

I am trying to open a submenu, and that when selecting one of its options the submenu closes, for this I add and remove the class show as seen in the following code
$open.addEventListener("click", (e) => {
e.preventDefault();
$submenu.classList.add("show");
document.documentElement.addEventListener("click", () => {
$submenu.classList.remove("show");
});
$containertopics= document.getElementById("container_topics");
$listtopics= document.getElementsByClassName("list__topics");
loadsubmenu();
for (let i of $listtopics) {
i.addEventListener("click", (e) => {
$submenu.classList.remove("show");
});
}
});
but the problem is that clicking on an item in the submenu does not remove the class, I have already tried to remove all its classes and add them one by one except the class see, but it doesn't work for me.
This code works:
$open.addEventListener("click", (e) => {
e.preventDefault();
loadsubmenu()
$submenu.classList.toggle("show");
});
for (let i of $listtopics) {
i.addEventListener("click", (e) => {
$submenu.classList.remove("show");
});
}
window.addEventListener('click', (e) => {
if (e.target == $submenu) {
$submenu.classList.remove("show");
}
})
There was some problem with eventListeners as you are assigning a function every time '$open' is clicked.
You haven't shared your HTML code here to know to us what is the error generate from your code. So I have created a example for you. Get the Idea. You can use .removeClass method.
$(document).ready(function(){
$("button").click(function(){
$("p").removeClass("highlight");
});
});
.highlight{
background: yellow;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p class="highlight">Remove class from the element</p>
<button type="button">Remove Class</button>
here is the JSfiddle

Drag and Drop API with React not dropping or returning id

I'm following this tutorial and I'm having trouble with the onDragStart and onDragOver not returning and information. More context, I'm making a sticky note type app.
onDragStart = (e, id) => {
e.dataTransfer.setData("id", id);
};
onDragOver = (e) => {
e.preventDefault();
};
onDrop = (e, sect) => {
let id = e.dataTransfer.setData("id", e.target.id);
let notes = this.state.notes.filter((note) => {
console.log(id);
if (note.key == id) {
note.section = sect;
}
return note;
});
this.setState({
...this.state,
notes,
});
};
This is the code I used that is not working properly. I can drag the notes but they don't drop. I believe this is because variable id is undefined for some reason. For example, if I removed the (note.key == id) the notes would drop correctly into place (but every note would and not the specific one I want).
As for the Sticky notes and droppable area, here is the code.
this.state.notes.forEach((n) => {
sections[n.section].push(
<Sticky
key={n.key}
onDragStart={(e) => this.onDragStart(e, n.key)}
id={n.key}
text={n.text}
/>
);
});
return (
<div className="App">
<div className="container-drag">
<h2 className="header">General</h2>
<div
id="general"
className="droppable"
onDragOver={(e) => this.onDragOver(e)}
onDrop={(e) => {
this.onDrop(e, "general");
}}
>
{sections.general}
</div>
</div>
<div className="container-drag">
<h2 className="header">Todo</h2>
<div
id="todo"
className="droppable"
onDragOver={(e) => this.onDragOver(e)}
onDrop={(e) => {
this.onDrop(e, "todo");
}}
>
{sections.todo}
</div>
</div>
</div>
);
}
And the sticky component is simply
<div id={this.props.id} className="draggable" draggable>
<p>{this.props.text}</p>
</div>
I don't think the problem is with the state so I'll leave that out, it is just an array of objects. In addition, I've tried using text instead of id but I was a bit confused so maybe I did something incorrectly. Basically, I've tried console.logging everything individually with no success. I'm not sure the problem but I found that copy-pasting the tutorials code made it work perfectly (but I still wanted my own take of it, with separate components).
Hopefully I can find a solution and I appreciate any help or tips.
I solved the problem.
onDragStart = (e, id) => {
e.dataTransfer.setData("id", id); //in brackets should be ("text/plain", id) and in the .getData below it should be (text", id)
};
onDragOver = (e) => {
e.preventDefault();
};
onDrop = (e, sect) => {
let id = e.dataTransfer.setData("id", e.target.id); //this shoud be .getData not .setData
And finally,
<Sticky
key={n.key}
onDragStart={(e) => this.onDragStart(e, n.key)}
id={n.key}
text={n.text}
/>
The onDragStart was being passed as a property not an actual function, I solved this by changing it to a

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

Categories

Resources