How to detect if child is clicked on parent click React - javascript

I'm using React and I have the following situation.
I have one parent div with onClick event, which takes full width and inside that div I have an image. I want to be able to know where it is clicked. Thus, I want to know if image (child) or div(parent) is clicked.
My code is as follows:
class APP extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<div className="parent" onClick={(e) => console.log("PARENT CLICK")}>
<img src="https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg"
style={{maxWidth: "60%", maxHeight: "90%", pointerEvents: 'none', zIndex: 99999}}
onClick={e => console.log("IMAGE CLICK")}
/>
</div>
)
}
}
ReactDOM.render(<APP />, document.querySelector("#app"))
But it detects always the parent click. I tried to add z-index to the child, but I think that child can't be in front of parent.
Here is the fiddle.

class APP extends React.Component {
constructor(props) {
super(props)
}
handleclick(e){
e.stopPropagation();
console.log(e.target.tagName);
return false;
}
render() {
return (
<div className="parent" onClick={(e) => this.handleclick(e)}>
<img src="https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg"
style={{maxWidth: "30%", maxHeight: "30%", zIndex: 99999}}
onClick={(e) => this.handleclick(e)}
/>
</div>
)
}
}
ReactDOM.render(<APP />, document.querySelector("#app"))
please note here I added e.stopPropagation() in the click event which only executes with the target element.. here you can read more about propogation
and also please remove the CSS pointerEvents: 'none' from the img tag, it works fine.
Working Fiddle

pointerEvents : none will block the pointer events.Remove that from your styling

You have pointerEvents set to none in your img's inline style object. Remove that. Can remove zIndex as well.
From CSS-Tricks:-
pointer-events:none prevents all click, state and cursor options on
the specified HTML element
You don't need e.stopPropagation() here. Just set the event handler only on parent like so (this is known as event delegation) :-
class APP extends React.Component {
constructor(props) {
super(props)
}
handleclick(e){
console.log(e.target.tagName);
}
render() {
return (
<div className="parent" onClick={this.handleclick}>
<img src="https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg"
style={{maxWidth: "30%", maxHeight: "30%"}}
/>
</div>
)
}
}
ReactDOM.render(<APP />, document.querySelector("#app"))

Im not sure if this is the react way of doing this but in javascript you can use the event object properties to get the element that triggered it with event.target and the element that handled it with event.currentTarget.
document.querySelector('#parent').addEventListener('click', (e) => {
console.log(`clicked element is: ${e.target.id}`);
console.log(`event handler element is: ${e.currentTarget.id}`);
});
<div id='parent'>parent
<div id='child'>child</div>
</div>

As CodeBug noted above it should be enough to stop the propagation on the click event by calling (inside of the onClick function):
event.stopPropagation();

Related

How to access child component node from parent

I'm trying to implement a feature to close a menu component when clicking outside of it. To achieve this I check if current target is present in ref node. The problem is when I click on Icon component... Inspecting it, e.target happens to be img from Icon component, and if I search it on ref.current, it's not present... Is there a way to link parent and child nodes together to achieve this condition ref.current.contains(e.target) when i click on a child component?
Parent component:
function Menu ({showMenu, close}) {
const ref = useRef(null)
useEffect(() => {
document.addEventListener('click', handleClickOutside)
return () =>{
document.removeEventListener('click', handleClickOutside)
}
}, [])
function handleClickOutside(e) {
if (ref.current && !ref.current.contains(e.target) && showMenu) {
close()
}
}
return (
<div ref={ref}>
<Icon action={openMenu2}/>
<h1>Menu</h1>
</div>
)
}
Child
function Icon ({action}) {
return (
<div onClick={() => action()}>
<i>
<img src={imageSrc} alt="icon"></img>
</i>
</div>
)
}
if u just want to use child Component's ref , u can pass ref to props.action
//Child
const cRef = useRef(null)
function Icon ({action}) {
return (
<div ref= onClick={() => action(cRef)}>
<i>
<img src={imageSrc} alt="icon"></img>
</i>
</div>
)
}
then u can use it in parent Component
or u can move those logic to child component
To achieve this I check if current target is present in ref node. The problem is when I click on Icon component... Inspecting it, e.target happens to be img from Icon component, and if I search it on ref.current, it's not present...
You can pass menu toggle function directly to down children to children,
Toggle Function
With this approach you don't need to check if showMenu is true because
this function will close the menu if it is open, and it will open if it is close
Assuming your state is at the parent of Menu component
const toggleMenu = () => {
setMenuOpen(!menuOpen);
};
Then in Menu component pass menuOpen as menu's state and toggleMenu as function to change it.
function Menu({ toggleMenu, menuOpen }) {
return (
<div style={{ display: "flex", alignItems: "center" }}>
<Icon toggleMenu={toggleMenu} />
<h1>Menu : {`${menuOpen ? "open" : "closed"} in Menu component`}</h1>
</div>
);
}
And in Icon component you can toggle the menu
function Icon({ toggleMenu }) {
return (
<div onClick={toggleMenu}>
<img
style={{ width: 35, cursor: "pointer" }}
alt="hamburger-menu"
src="https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/menu-alt-512.png"
/>
</div>
);
}
export default Icon;
In action on codesandbox :https://codesandbox.io/s/eager-joliot-zb3t7?file=/src/Menu.jsx:54-299
If your app is getting complicated with passing states and passing state change functions I recommend you to check redux pattern.
Here link for redux : https://react-redux.js.org/

What is the event that will trigger click release-Javascript/Reactjs

I'm trying to highlight a element on click and on release of click event anywhere on the page I want to de-highlight the same element:
fidde:
https://jsfiddle.net/3jvpg9t1/2/
code:
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
activeState: false
};
}
onmouseup() {
this.setState({active: false});
}
onClick() {
this.setState({active:true});
}
render() {
const bgcolor = this.state.active ? "#e9e9e9" :""
return <div>
<span style={{backgroundColor: bgcolor}}onClick={this.onClick.bind(this)} onMouseUp={this.onmouseup.bind(this)}>helo world</span>
{this.state.active &&
<select><option>A</option><option>B</option></select>}
</div>
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
but for some reason I'm not getting the event that will trigger when the mouse click is release.
I tried: onMouseUp, onBlur
any ideas?
I've never found a good way of doing this with just React, so I've fallen back on vanilla JS event listeners.
document.body.addEventListener('click', this.handleBodyClick);
This will listen for a click on the body and will fire off the function which will set the active state to false.
Here it is in action: https://jsfiddle.net/sq2ajn8o/30/
If you want to have the active state toggle on click you should use a function that does something like this:
handleClick() {
const { activeState } = this.state;
this.setState({ activeState: !activeState });
}
If the active state is true, it will change to false and vice versa.

How to avoid conflicting onclick functions on a React component

I have a component below: there are 2 usecases, when I click on the entire div some function is executed and when I click on just the <img>, some function is executed.
I have 2 props defined, onClick, when clicked on the entire div
and onIconClick when clicked on the img.
export default class List extends React.Component {
onClick() {
this.props.onClick();
}
onIconClick() {
this.props.onIconClick();
}
render() {
return (
<div style="width:200px, height: 200px" onClick={this.onClick.bind(this)}>
<img src="delete.png" onClick={this.onIconClick.bind(this)} />
</div>
);
}
}
here is the way I call the component:
export default class MyApp extends React.Component {
onClick() {
//some execution
}
onIconClick() {
//some other execution
}
render() {
return (
<List
onClick={this.onClick.bind(this)}
onIconClick={this.onIconClick.bind(this)}
/>
);
}
}
Now my issue is the this.onClick.bind(this) gets called all the time even when I click the image, hence I never get to this.onIconClick.bind(this).
I tried reversing the order:
<List onIconClick={this.onIconClick.bind(this)} onClick={this.onClick.bind(this)} />
still doesn't work, not sure what selection criteria I should use in order to distinguish between these 2 clicks.
Any ideas??
Try this:
class Parent extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.onIconClick = this.onIconClick.bind(this);
}
onClick() {
alert("Parent-onClick()");
}
onIconClick() {
alert("Parent-onIconClick()");
}
render() {
return (
<div>
<Child onClick={this.onClick} onIconClick={this.onIconClick} />
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.onIconClick = this.onIconClick.bind(this);
}
onClick() {
this.props.onClick();
}
onIconClick(e) {
e.stopPropagation();
this.props.onIconClick();
}
render() {
let styles = {
height: "500px",
backgroundColor: "blue",
paddingTop: "20px"
};
return (
<div onClick={this.onClick} className="img-wrapper" style={styles}>
<img
onClick={this.onIconClick}
src="https://via.placeholder.com/350x150"
alt="img"
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"><div>
The idea is using stopPropagation() method of the SyntheticEvent instance.
Your event handlers will be passed instances of SyntheticEvent, a
cross-browser wrapper around the browser’s native event. It has the
same interface as the browser’s native event, including
stopPropagation() and preventDefault(), except the events work
identically across all browsers.
If you find that you need the underlying browser event for some
reason, simply use the nativeEvent attribute to get it.
Using the SyntheticEvent 's methods allows our code to behave exactly the same across all browsers.
You can use e.stopPropagation(). If you want to use e.nativeEvent.stopImmediatePropagation(), you can use in the same line.
onIconClick(e) {
e.stopPropagation();
//e.nativeEvent.stopImmediatePropagation();
this.props.onIconClick();
}

div's id attribute is undefined/not getting captured onClick in a react app

I'm mapping data from the api response and rendering multiple divs out of it. Along with that I'm assigning a unique id from the response to the id attribute of each div like this:
...lists.map(list => {
return (
<div className='one' key={list.id} id={list.id} onClick={this.handleClick}>
<div className='two'>
<p>Hello World</p>
<span>Foo Bar</span>
</div>
</div>
)
})
handleClick = (e) => {
console.log(e.target.id)
// other stuff
}
The Problem:
Whenever the outer div (className='one') is clicked the console logs undefined. However, if I assign the id value to the inner div (className='two') it logs the value of id only if the click is made within the dimensions of the inner div. Same is the case with the <span> and <p> tags.
Basically, the onClick returns a different target on clicking different html elements.
Expected result:
Clicking the parent div or anywhere inside that div should always return the value of the id attribute of the parent div.
The thing is when you define onClick on the topMost parent, you need to use e.currentTarget.id instead of e.target.id since e.target will give you the element on which you clicked rather then the parent on which onClick listener is defined
class App extends React.Component {
state = {
lists: [{id: 1}, {id: 2}, {id:3}]
}
render() {
return (
<div>
{this.state.lists.map(list => {
console.log(list.id)
return (
<div className='one' key={list.id} id={list.id} onClick={this.handleClick}>
<div className='two'>
<p>Hello World</p>
<span>Foo Bar</span>
</div>
</div>
)
})
}
</div>
)
}
handleClick = (e) => {
console.log(e.currentTarget.id)
// other stuff
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="app"></div>
Ok, the problem isn't Reactjs, the problem is the event target.
You are using e.target when you have to use event.currentTarget, here is the difference.
target is the element that triggered the event (e.g., the user clicked on)
currentTarget is the element that the event listener is attached to.
Let see this in an example:
let tacos = [{
person: "John",
ingredient: 'Guacamole'
}, {
person: 'Sally',
ingredient: 'Beef'
}];
class App extends React.Component {
render() {
return (
<div>
<h3>List of tacos:</h3>
<TacosList tacos={tacos} />
</div>
);
}
}
class TacosList extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
const currentTarget = event.currentTarget;
console.log(event.target);
console.log(currentTarget.id);
}
render() {
return (
<div>
{this.props.tacos.map((taco, index) => (
<div className="one" id={`reference-${index}`} key={index} onClick={this.handleClick}>
<p>{taco.person}: {taco.ingredient}</p>
</div>
))}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.one {
padding: 20px;
}
<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="root">
</div>
Note that you can click on the div and p element and both will trigger the event, in the case of p it will propagate the event up to the div , therefore, it's when the event target changes
In the constructor, just put this line:
constructor() {
this.handleClick = this.handleClick.bind(this);
}
After this, you will be able to access the ref to the element,
For more detail please go through this link :
https://reactjs.org/docs/handling-events.html
try to set event as function param
handleClick = (e) => {
console.log(e.target.id)
// other stuff
}
if you are already using ES6
I would change the code a bit, so it will easier to understand and work with no special glitches with events target and attributes
lists.map(list => {
return (
<div
className='one'
key={list.id}
onClick={this.handleClick.bind(this, list.id)
<div className='two'>
<p>Hello World</p>
<span>Foo Bar</span>
</div>
</div>
)
})
handleClick = (listId) => {
console.log(listId)
// other stuff
}
as you can see here, I just call the method with the list id and I'm done

Toggle class except if a child is clicked in React

I want to have a header where you can click it to hide/show it. But on this header, there will be a group of buttons that would be part of a child element. If you click these buttons, I don't want the whole thing collapse.
How can I achieve this in React? What I have so far is going to collapse everything, because Child is in Parent and under row
class Parent extends Component {
constructor() {
super();
this.state = {
show: false
}
}
render() {
return (
<div className="row" onClick={() => this.setState({show: !this.state.show})}>
<div className="col-2">
<Child/>
</div>
...
</div>
)
}
}
You should be able to use stopPropagation() in the event handler for the buttons to prevent it from bubbling further. See http://facebook.github.io/react/docs/events.html for API details.
class Child extends Component {
handleClick(event) {
event.stopPropagation();
doSomething();
}
render() {
return (
<button onClick={e => this.handleClick(e)}>
Click Me!
</button>
);
}
}
In Child's onClick event handler,
add this line
event.stopPropagation()

Categories