This is my code,
export default myComponent = () => {
const handleClick = (e) => {
const parent = e.target.parentElement;
if (parent.classList.contains('selected_category')) {
parent.classList.remove('selected_category');
} else {
parent.classList.add('selected_category');
}
};
return (
<>
<ul>
<li className="">
<a onClick={handleClick}>
content<span class="text-gray-25 font-size-12 font-weight-normal">
121
</span>
</a>
</li>
<li className="">
<a onClick={handleClick}>
content<span class="text-gray-25 font-size-12 font-weight-normal">
121
</span>
</a>
</li>
</ul>
</>
);
}
I wrote this code for when I clicked tag element the parent element of it gets the class 'selected_category' if it doesn't already have. But the problem here is when I click children class then 'selected_category' class is added to parent tag. is there any solution to prevent it?
This is my code in sand box
To further elaborate on my comment: the issue comes from the use of e.target, which can refer to the element where the event listener is bound to OR its descendant(s). In this case, your <a> tag has <span> as a child. When a click event bubbles up to your <a> tag that originated from the inner <span>, then e.target will refer to the latter: which is not what you want.
To ensure that you get the reference to the actual element which the event listener is bound to is all times, you need to use e.currentTarget:
The currentTarget read-only property of the Event interface identifies the current target for the event, as the event traverses the DOM. It always refers to the element to which the event handler has been attached, as opposed to Event.target, which identifies the element on which the event occurred and which may be its descendant.
Therefore your updated function should simply use currentTarget instead, i.e.:
const handleClick = (e) => {
// NOTE: Use e.currentTarget here instead of e.target
const parent = e.currentTarget.parentElement;
if (parent.classList.contains('selected_category')) {
parent.classList.remove('selected_category');
} else {
parent.classList.add('selected_category');
}
};
Related
I'm trying to make a SPA with html, css and vanilla JS (I have very little idea of JS). The problem I have is that the method I am using, works correctly in my header where I have only "a" with text inside. But when I want to use an img as "a", the script does not work inside the img, it only allows me to click around the img and not inside it.
I appreciate any help.
This is my script and my html in the part where I have the problem.
const route = (event) => {
event = event || window.event;
event.preventDefault();
window.history.pushState({}, "", event.target.href);
handleLocation();
};
const routes = {
404: "./pages/404.html",
"/": "./pages/index.html",
"/vehicles": "./pages/vehicles.html",
"/services": "./pages/services.html",
"/contact": "./pages/contact.html",
"/financing": "./pages/financing.html",
"/locations": "./pages/locations.html",
};
const handleLocation = async () => {
const path = window.location.pathname;
const route = routes[path] || routes[404];
const html = await fetch(route).then((data) => data.text());
document.getElementById("main-page").innerHTML = html;
};
window.onpopstate = handleLocation;
window.route = route;
handleLocation();
<a href="/financing" onclick="route()" class="mainServices-section">
<div class="main-title">
<h2>Financing</h2>
</div>
<img src="../image/financing-image-colored.svg" alt="">
</a>
The issue is that when you click the <img> the target isn't set to your <a> tag so event.target.href is undefined.
Instead of using onclick attributes, use a delegated event listener and check if it originates from an <a> tag or a descendant.
document.addEventListener("click", (e) => {
const anchor = e.target.closest("a[href]");
if (anchor) {
e.preventDefault();
window.history.pushState({}, "", anchor.href);
handleLocation();
}
});
Demo ~ https://jsfiddle.net/dgn1r5zh/
This behavior happens because event.target is a reference to the element that fired the event. In this case that is your image.
First of let me point out that inline event handlers should not be used. They can create unexpected behavior and make you write JS inside HTML. More about this topic. Learn to use addEventListener instead.
You can fix your issue by adding some CSS to your element that you want to click. With CSS the pointer-events you can determine if an element should be able to become a target.
Implement this by selecting all the elements within the element that you want to have as your target and set pointer-events: none; to disable any pointer events on those elements. This ensures that the <a> tag is the only element that can be considered a target. So event.target in your code will always point to the correct element.
.mainServices-section * {
pointer-events: none;
}
Alternatively, if you can't use pointer-events because, for example you need some other pointer event to fire on the children of .mainServices-section, then you should extend your code by explicitly checking if the current target is the <a> tag and otherwise select the <a> tag if possible.
You can search for the <a> from any child within the element with the closest that is on the clicked element. It walks up and looks for the element that you're searching for.
const route = (event) => {
const target = event.target.closest('.mainServices-section');
if (target === null) {
return;
}
event.preventDefault();
window.history.pushState({}, "", target.href);
handleLocation();
};
const mainServicesSections = document.querySelectorAll('.mainServices-section');
for (const mainServiceSection of mainServicesSections) {
mainServiceSection.addEventListener('click', route);
}
You need to look for the .closest("a") of the clicked element ev.target:
var hist="none";
const route = (event) => {
event = event || window.event;
event.preventDefault();
hist=event.target.closest("a").getAttribute("href");
handleLocation();
};
const routes = {
404: "./pages/404.html",
"/": "./pages/index.html",
"/vehicles": "./pages/vehicles.html",
"/services": "./pages/services.html",
"/contact": "./pages/contact.html",
"/financing": "./pages/financing.html",
"/locations": "./pages/locations.html",
};
const handleLocation = async () => {
const route = routes[hist] || routes[404];
console.log(route);
// fetch() etc. to be placed here ...
};
// window.onpopstate = handleLocation;
// window.route = route;
handleLocation();
<a href="/financing" onclick="route()" class="mainServices-section">
<div class="main-title">
<h2>Financing</h2>
</div>
<img src="https://picsum.photos/200" alt="">
</a>
Another thing I changed is the way to look for the href attribute of the <a> element: DOMelement.href will return an absolute path, starting with "https://" while DOMelement.getAttribute("href") will actually get you the string as defined in the element's attribute.
I have a div element that I want to call a function from when a click event fires on it. The div contains an input field that I don't want to trigger the function, so I'm using useRef to target only the div element itself and not the contents. I'm creating the reference with the hook, giving the div a ref attribute pointing to that reference, and then passing it an onClick event that first checks if ref.current is equal to the event target. This method has worked for me in the past when building features such as modals, but for some reason this time the value of ref.current and the value of the event target are not evaluating as equal, and so the function never fires off. Here's the code:
const VideoEntryTile: React.FC = () => {
const [isExpanded, setIsExpanded] = useState<boolean>(false);
const containerRef = useRef<HTMLDivElement | null>(null)
const handleExpand = (e: any):void => {
console.log(containerRef === e.target) // This evaluates to false
if (containerRef.current === e.target) {
setIsExpanded(true)
}
}
return (
<div
className={styles.VideoEntryTile__Container}
onClick={handleExpand}
ref={containerRef} {/* The reference is set here */}
>
<div className={styles.VideoEntryTile__Top}>
<div className={styles.VideoEntryTile__LeftActions}>
<div className={styles.VideoEntryTile__LeftActions__VideoNameInput}>
<TextField
disabled={false}
inputSize="large"
value="Video 1"
/>
</div>
<div className={styles.VideoEntryTile__LeftActions__GuestsContainer}>
<p>Guests</p>
<input />
</div>
<button type="button">-</button>
<button type="button">+</button>
</div>
<div className={styles.VideoEntryTile__RightActions}>
<p>20</p>
<p>Credits</p>
</div>
</div>
{ isExpanded ?
(
<div className={styles.VideoEntryTile__Bottom}>
<p>Test</p>
<p>Test</p>
<p>Test</p>
<p>Test</p>
<p>Test</p>
</div>
)
: null }
</div>
);
};
Any idea why this isn't working? This solution has worked for me in the past so I'm not sure why it isn't now.
E: This thread should not have been closed - the provided "answer" has nothing to do with my question.
I know this is an old topic and the question is very old, but maybe it will be useful to someone. 🤷♂️
Code:
const handleExpand = (e: any):void => {
if (containerRef.current.contains(e.target)) {
setIsExpanded(true)
}
}
Problem:
In your case useRef is returning the current as an Object. By comparing the e.target which is a reference to the object onto which the event was dispatched and an Object the comparison will never be equal.
Solution:
Check if the reference to the object (returned by e.target) is contained inside the useRef.current node.
I want to add a new className when the user hovers on a slick slider Image and perform some CSS transition for that particular Image card in the slider. https://stackblitz.com/edit/react-slick-slider-issues how do I add className to the slider whenever the user hovers on the image or change the parent className based on the hover position?
I tried the document.getElementsByClassName('unique-image') but all the images have this className as they are looped inside a map function. how can I only change unique-image className if the user hovers on a certain image to unique-image-hover?
You may access Event.target that triggered mouseEnter and use Element.classList add() method to add your desired className
So, your mouseEnter handler may look like that:
const mouseHover = e =>{
e.target.classList.add('someClassNameYouWantedToAdd')
}
I can use React.useState
const [hoveredClass, setHoveredClass] = React.useState("");
const updateHovered = (e) => {
setHoveredClass(e.target.id)
}
const removeHovered = (e) => {
setHoveredClass('')
}
return (
<div className={`someStaticClass ${hoveredClass ? "hoveredClass" : ""}`}
onMouseEnter={updateHovered}
onMouseExit={removeHovered}
>
{list. map(item => (
<ImageChildComponent {...item} />
)}
</div>
)
Target will give you a child element, but you can add an event listener to the parent.
As #YevgenGorbunkov mention, change in state will trigger rerendering, so
consider wrapping ImageChildComponent with React memo to prevent unnecessary rendering
I have started an application which I want to work same as weather.com next 36 hours section. The idea is when you click on each weatherCard which has a seperate component in my app you will update the below section which is my weatherDetails component based on the selected weatherCard /weather box. So I made the entire component clickable by giving it the click event via props from my stateful component which is my weatherLocation component. This is my WeatherCard component:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={props.clicked}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
And here in render method in WeatherLocation component I loop through data coming from state and give props the WeatherCard component:
const WeatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
{...report}
clicked={() => this.handleCardClick(event)}
/>
);
});
And this is the handleCardClick that I added for it just for testing:
handleCardClick = event => {
// const { reports , selectedCardInfo , activeCard } = this.state;
const selectedDate = document.getElementById(event.target.id);
console.log(event.target.id);
}
I don't want to use anchor tag as I don't need href. The click works fine by itself. But because I need to get the id of the parent which is the div with the class of weatherCard. At the moment when I click on other elements inside the card I cannot get the id because they are not the parent. The reason I need its id is when I get data with from the API I need a unique value for each card so that when you click on the card the data for that card will be shown in the other component which is the WeatherDetails component. But for now I need to be able to somehow choose that selected card and pull out the state for that unique card. Could someone help me out? Thanks.
You just need to pass the Parent component ID to your onClick function in Weather Card.
Here is your WeatherCard - Component
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
You can see that I have added props.id to your onClick function and with help of event now you can access that id from the parent component.
Now here is your Parent Component- WeatherCards
const WeatherCards = this.state.reports.map( (report, i) => {
return(
<WeatherCard
key={report.id}
id={i}
{...report}
clicked={this.handleCardClick}
/>
);
});
You can see in the code I am passing index number as id to your child component.
So this will give you an id (for now it's an index number) of the card in your onClick handler.
and Finally, here is your on click handler.
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
As of now, I am using the index as id if you want to use a unique identifier, you can change that easily.
General JavaScript solution is to differentiate the elements and .stopPropogation after you've captured the event you are targeting. A nested unordered list, <ul>would be an example. Tag the containing <li> with an .opened class upon rendering/displaying each level of nesting, tag those <li> elements accordingly, e.g. a dataset attribute such as data-make, then data-model, then data-option. You then attach and fire event listeners on the different level <li>'s.
Thank you #RutulPatel. I made your answer as the answer. But I changed your code a bit as I got your point so I wrote an answer as it is long. I think we might not need to change the WeatherCard at all and I don't pass event or any logic there. so it will be intact:
const WeatherCard = (props) => (
<div id={props.date} className="weatherCard" onClick={event => props.clicked(event, props.id)}>
<h2 className="cardDate">{props.date}</h2>
<h4>{props.forcast}</h4>
<div className="minmaxDeg">
<data>{props.min}</data>
<data>{props.max}</data>
</div>
<data>{props.rain}</data>
</div>
);
But I use your tip changing my weatherCards array to look like this:
const weatherCards = this.state.reports.map( report => {
return(
<WeatherCard
key={report.id}
id={report.date}
{...report}
clicked={() => this.handleCardClick(event, report.date)}
/>
);
});
So I use the report.date which is a unique value as my id. Also I don't pass event as a parameter to the arrow function I just pass it with the report.date to the handler:
clicked={() => this.handleCardClick(event, report.date)}
And the handler will be the same as you did:
handleCardClick = (event, weatherCardID) => {
console.log(weatherCardID)
}
I might even remove event later on from both if there was no need fo that.
Thank you again.
var mark = null;
class Demo extends React.Component {
handleClick(evt) {
mark = "outer";
}
handleSpanClick(evt) {
mark = "inner";
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>
<span onClick={this.handleSpanClick.bind(this)}>
inner
</span>
</div>
)
}
}
For example, I expect that the mark will be "inner" when I click span, but actually, the mark will be "outer". I know the onClick event of span will be called firstly, so I cant get "inner".
How can I get "inner" in this sample?
Example for Bubbling and Capturing in React.js
Bubbling and capturing are both supported by React in the same way as
described by the DOM spec, except for how you go about attaching
handlers.
<div onClickCapture={this.handleClick.bind(this)}>
...