I have multiple elements with same className and i want if some element (with className history-node) is clicked it should get className active along with current className.
But i am having an issue, there are childs of that element and if child elements gets clicked they also get class Active.
Here is the code:
<div className="history-node-container" key={index}>
<div className="history-node" onClick={(e) => {this.handleHistoryClick(e.target)}}>
<span className="history-title">{heading.action}</span>
<span className="history-date">{moment(heading.added_at).format("MMMM Do, YYYY")}</span>
</div>
</div>
handleHistoryClick function
handleHistoryClick(target){
$('.history-node').removeClass('active'); //removing active class from all elements
target.className = 'history-node active';
}
I want to run function when user click on element with className history-node
But if user clicks on history-title, ClickHandler gives class active to history-title element.
EXPECTED BEHAVIOUR: if history-node is clicked only history-node should get class active.
One tip and how to solve your problem (In two ways)
Tip: Not usually the best idea to mix React with jQuery. React came in as a major paradigm shift in how we interact with the DOM. Try to read a little bit more about React, how it works, why is it so different from simply adding/removing elements in the DOM with jQ.
Some references to help you with that:
How to go from jQuery to React.js?
Thinking in React for jQuery Programmers
Now, back to your question
You should use currentTarget.
As the .history-title and .history-date elements are wrapped within .history-node, any click on them will trigger it's parent's event, since .history-node body is .history-title + .history-date. That's the correct behavior. When you trigger an event in JS, the event object receives two parameters: target, which is the event triggering the event and currentTarget, the element that is actually listening for the event.
Now for the code:
with JQ
Component:
<div className="history-node-container" key={index}>
<div className="history-node" onClick={handleHistoryClick}>
<span className="history-title">{heading.action}</span>
<span className="history-date">{moment(heading.added_at).format("MMMM Do, YYYY")}</span>
</div>
</div>
Click:
handleHistoryClick(event){
$('.history-node').removeClass('active')
event.currentTarget.classList.add('active')
}
The React way (Pure React, no modules)
Component:
class HistoryNode extends Component {
constructor(props) {
super(props)
this.state = { isActive: false }
this.handleClick = this.handleClick.bind(this)
}
handleClick(e) {
let state = this.state
this.setState({isActive: !state.isActive})
}
render() {
return(
<div className="history-node-container">
<div className={`history-node ${this.state.isActive ? 'active' : ''}`} onClick={handleHistoryClick}>
<span className="history-title">{heading.action}</span>
<span className="history-date">
{moment(heading.added_at).format("MMMM Do, YYYY")}</span>
</div>
</div>
)
}
}
Notice how you don't need to manipulate the DOM at any moment at the React solution. You just attach your event to that particular component, define how it's state change should reflect on the UI and let React do the rest.
Hope it helps ;)
Reference for target vs currentTarget
I think the event propagates to child components.
Have you tried this ?
<div className="history-node-container" key={index}>
<div className="history-node" onClick={handleHistoryClick}>
<span className="history-title">{heading.action}</span>
<span className="history-date">{moment(heading.added_at).format("MMMM Do, YYYY")}</span>
</div>
</div>
HandleClick function
handleHistoryClick(event){
event.stopPropagation();
$('.history-node').removeClass('active');
event.target.className = 'history-node active';
}
EDIT : You could make it simpler though (and without jQuery) using your component state. But without knowing how you wrote your component I cannot give you a snippet illustrating it. Be careful too as you interact directly with the DOM, this implies a performance loss. Using the React state allows you to avoid such thing!
Related
I'm working on new web-components and ran into some issues concerning slots.
I have an outer container-component with a "main-slot" inside, in which multiple elements should be inserted (into the same slot). However, it is only possible to add one element per named slot.
My question: is there a way to add multiple elements to one named slot? Like shown here:
<own-container>
<own-element slot="main"></own-element>
<own-element slot="main"></own-element>
<own-element slot="main"></own-element>
<own-element slot="main"></own-element>
</own-container>
There is also imperative <slot>
super().attachShadow({
mode: 'open',
slotAssignment: 'manual' // imperative assign only
})
But! you get Named slots OR Imperative slots, on one Web Component
To mimic named <slot>, assigning content to the same named <slot>
you probably need the Mutation Observer API
addendum
You can have multiple elements per slot:
<component-with-slots>
<H1 slot="title">Web Components are great!</H1>
<h2 slot="title">A bit hard to master</h2>
<b slot="title">But Great!</b>
</component-with-slots>
<script>
customElements.define('component-with-slots', class extends HTMLElement {
constructor() {
super()
.attachShadow({mode:'open'})
.innerHTML="<slot name='title'></slot>";
}
});
</script>
Nope. It is not possible for named slot. The trick is to have a wrapper div element to wrap your lightDOM.
<own-container>
<div slot="main">
<own-element></own-element>
<own-element></own-element>
<own-element></own-element>
<own-element></own-element>
</div>
</own-container>
If the presence of additional div causes styling problem, then you can use new contents type of display box.
div {
display: contents;
}
The display: contents causes an element's children to appear as if they were direct children of the element's parent, ignoring the element itself. However, note that it can cause accessibility issues.
In the below code I am trying to assign <span slot='test-slot'>b</span> to <slot name='test-slot'>a</slot> but the assignment does not work. If I bring <span slot='test-slot'>b</span> outside of its parent <div> container the assignment does take place as expected.
Why is this? Is there anyway you can assign from nested elements with the slot element? If not, any alternatives? This is obviously a reduced test case but in my real web component, it is much more intuitive for a user to add an element with the slot tag within other containers.
<test-element>
<div>
<span slot='test-slot'>b</span>
</div>
</test-element>
<template id='template-test-element'>
<slot name='test-slot'>non slotted content</slot>
</template>
<script>
class TestElement extends HTMLElement {
constructor() {
let template = document.getElementById("template-test-element")
.content.cloneNode(true);
// Initialise shadow root and attach table template
super() // sets AND return 'this' scope
.attachShadow({mode:"open"}) // sets AND returns shadowRoot
.append(template);
}
}
customElements.define('test-element', TestElement);
</script>
Named slots only accept top-level children that have a matching slot-attribute.
See this (old) Polymer explainer or this more recent article.
Edit: Not sure where this is coming from though, the spec fails to mention this requirement: https://html.spec.whatwg.org/multipage/dom.html#attr-slot
It also is neither mentioned here nor here.
I want to change CSS of element <div class="myClass1">"I am content"</div> in react. Basically, I just want to add "display: none;" Problem is that that element is created by one library on click so I cant really set style or class to state or variable or something. In jQuery there is a lot of ways but how to do this in react?
Here is my handler
// Handling change
const handleChange = () => {
// Here should be code to change css of myClass1
};
You can use conditional class name for that. In your handleChange set a boolean state and in your className use this state to change className. Then in this new className you can apply display: none .
<div class={`${clicked ? "myClass1" : "myClass2"}`}>"I am content"</div>
In this case clicked would be your state that you can change with handleChange function
I have a div, and inside I am rendering spans based on some conditions. If the component has some children, I'd like this div to expand. It works fine if I click on this div on an area outside of the inner span.
An example, is the following Image. When clicking on the row, it expands the area to show items like this. However, when I click on the header text, it's not working, the on click event doesn't fire.
Here's my code:
<div className={headerStyles.header} onClick={(e) => this.selectHeader(e, this.props.items.length)}>
{this.props.items.length > 0 &&
<span className={headerStyles.expand} style={{ color: this.props.headerNameColor }}></span>
}
<span style={{ color: this.props.headerNameColor }} >{this.props.headerName}</span>
{this.props.headerUrl &&
<a
style={{ color: this.props.headerNameColor }}
href={this.props.headerUrl}
data-interception="off"
target="_blank"
title="Open link in a new tab">
<i className={['ms-Icon', 'ms-Icon--OpenInNewWindow', headerStyles.openNewTab].join(' ')}></i>
</a>
}
</div>
Here's the function that gets called when clicking the header:
selectHeader(e, numOfItems) {
if (e.target.children.length > 0 && numOfItems > 0) {
e.target.children[0].classList.toggle(headerStyles.expand)
e.target.children[0].classList.toggle(headerStyles.collapse)
e.target.parentElement.querySelector(`.${headerStyles.items}`).classList.toggle(headerStyles.hidden)
}
else if(this.props.headerUrl){
}
}
Any guidance is appreciated.
Thanks
As I suspected, you're trying to do something with the event.target value inside of selectHeader. That won't work very well because event.target will change depending on which element you clicked on inside of your div. It won't always be the outer "parent" element.
You're also trying to read data imperatively from the DOM, which is not a very "React" way of doing things. In fact, within your function you're doing a classList.toggle(headerStyles.hidden) which is in direct conflict with React managing the state and rendering of your app. All the data you need should live within the state of your React application - you shouldn't need to query the DOM at all.
Ditch event.target and classList.toggle and find a way to do it using React state. For example, when you click the div you could simply toggle the "expanded" state of your component, and use that state to determine which CSS class to render:
<div onClick={() => {this.setState({isExpanded: !this.state.isExpanded})}} >
<span className={this.state.isExpanded ? headerStyles.expand : headerStyles.collapse} ></span>
</div>
Try to add pointerEvents: 'none' style to the span tags so it won't get any mouse event. This will make the event.target to always be the div
Avoid using arrow functions inside the JSX here. e object is always passed since this is an event handler, and your props are accessible from your handler:
selectHeader = (e) => {
const numOfItems = this.props.items.length;
// rest of the code...
}
inside render method:
<div onClick={this.selectHeader}>
First off I did look at all the related answers but none solved my problem. I have the following:
class TweetingBox extends React.Component{
render(){
return (
<div id="page-blackout" onClick={this.props.closeTweetingInterface}>
<div id="tweeting-box" onClick={(proxy) => {proxy.stopPropogation()}}>
<h5 id="tweeting-box-header">Compose new Tweet</h5>
<TweetingInterface initialContent={this.props.initialContent}/>
</div>
</div>
)
}
}
div with id="page-blackout" covers the entire page and div with id="tweeting-box" is centered on the page, containing a form in the component TweetingInterface. When I click anywhere, both in the form as well as on tweeting-box itself, this.props.closeTweetingInterface is called. How can I stop this?
All the sibling elements will get their parents property, so the parent function will get called closeTweetingInterface every time the parent or the sibling elements get called on.