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)}>
...
Related
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');
}
};
class Headers extends React.Component {
render() {
const selected = this.props.selectedPane;
const headers = this.props.panes.map((pane, index) => {
const title = pane.title;
const klass = index === selected ? 'active' : '';
return (
<li
key={index}
className={klass}
onClick={() => this.props.onTabChosen(index)}>
{title}{' '}
</li>
);
});
return (
<ul className='tab-header'>
{headers}
</ul>
);
}
}
export default class Tabs extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedPane: 0
};
this.selectTab = this.selectTab.bind(this);
}
selectTab(num) {
this.setState({selectedPane: num});
}
render() {
const pane = this.props.panes[this.state.selectedPane];
return (
<div>
<h1>Tabs</h1>
<div className='tabs'>
<Headers
selectedPane={this.state.selectedPane}
//onTabChosen={this.selectTab}
panes={this.props.panes}>
</Headers>
<div className='tab-content'>
<article>
hellooooo
{pane.content}
</article>
</div>
</div>
</div>
);
}
}
I'm currently creating a 3 tab section where if you click on a tab, it gives you a new pane.
When looking at the render function I see a custom tag called Headers.
I know it coming from the Headers class at the beginning, but how does that format work? Is that a custom tag we building?
Also when looking at its properties such as onTabChosen, when it is deleted in the render method (for learning purposes) and I click on a selected tab, an error comes up saying
"_this.props.onTabChosen is not a function".
this.props.onTabChosen(index).. was written in the Headers class but not as a function correct?
I guess because I am also confused on how this.props.onTabChosen(index) works since onTabChosen was never declared anywhere, just input after props.
When looking at the render function I see a custom tag called "Headers".
That is not a custom tag. That is a React Component.
I know it coming from the Headers class at the beginning, but how does that format work?
Headers is either a function or a class (i.e. a constructor function).
The function will be called and the first argument passed to it will be an object with properties and values that match the props on the JSX element.
If you're going to use React then read a tutorial, this is very introductory level stuff for the framework.
It is covered very early on in both the MDN tutorial and the official React tutorial.
I guess because I am also confused on how this.props.onTabChosen(index) works since onTabChosen was never declared anywhere, just input after props.
It was declared, just not in the piece of code you shared.
In React JSX I want to convert a part of the text into an anchor tag dynamically. Also on click of the anchor tag, I want to do some API call before redirecting it to the requested page. But I am failing to achieve this. Can anyone have a look and let me know where am I going wrong?
I have recreated the issue on code sandbox: here is the URL: Code Sandbox
Relevant code from sandbox:
import React from "react";
import "./styles.css";
export default function App() {
let bodyTextProp =
"This text will have a LINK which will be clicked and it will perform API call before redirect";
let start = 22;
let end = 26;
let anchorText = bodyTextProp.substring(start, end);
let anchor = `<a
href="www.test.com"
onClick={${(e) => handleClick(e)}}
>
${anchorText}
</a>`;
bodyTextProp = bodyTextProp.replace(anchorText, anchor);
const handleClick = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
const handleClick2 = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
return (
<div className="App">
<h3 dangerouslySetInnerHTML={{ __html: bodyTextProp }} />
<a href="www.google.com" onClick={(e) => handleClick2(e)}>
Test Link
</a>
</div>
);
}
The problem is variable scope. While it is entirely possible to use the dangerouslySetInnerHTML as you are doing, the onClick event isn't going to work the same way. It's going to expect handleClick to be a GLOBAL function, not a function scoped to the React component. That's because React doesn't know anything about the "dangerous" html.
Normally React is using things like document.createElement and addEventListener to construct the DOM and add events. And since it's using addEventListener, it can use the local function. But dangerouslySetInnerHTML bypasses all of that and just gives React a string to insert directly into the DOM. It doesn't know or care if there's an event listener, and doesn't try to parse it out or anything. Not really a good scenario at all.
The best solution would be to refactor your code so you don't need to use dangerouslySetInnerHTML.
*Edit: since you say that you need to do multiple replacements and simply splitting the string won't suffice, I've modified the code to use a split.
When used with a RegExp with a capturing group, you can keep the delimiter in the array, and can then look for those delimiters later in your map statement. If there is a match, you add an a
import React from "react";
import "./styles.css";
export default function App() {
let bodyTextProp =
"This text will have a LINK which will be clicked and it will perform API call before redirect";
let rx = /(\bLINK\b)/;
let array = bodyTextProp.split(rx);
const handleClick = (e) => {
console.log("The link was clicked.");
e.preventDefault();
};
const handleClick2 = (e) => {
e.preventDefault();
console.log("The link was clicked.");
};
return (
<div className="App">
<h3>
{array.map((x) => {
if (rx.test(x))
return (
<a href="www.test.com" onClick={handleClick}>
{x}
</a>
);
else return x;
})}
</h3>
<a href="www.google.com" onClick={(e) => handleClick2(e)}>
Test Link
</a>
</div>
);
}
My particular use case of React is thus:
I wish to add a small React Component to a card that is an existing, fully-functional HTML element, per all the cards on the page. This React Component shall serve to implement a new feature on those cards : reverting changes.
The HTML (well, the MVCE version of it)
is something like this:
<div id="some-id" class="card float-sm-left menu-visual-card " onclick="(function(event) { console.log('I got clicked, and a modal will spawn' ) })(event)">
<div class=card-block>
<h5 class="card-title format-text">Some title</h5>
<!-- some business elements here -->
</div>
<!-- card footer -->
<div class=customized-indicator-react></div>
</div>
The React Component
in its tl;dr version is the following:
class CustomizedIndicatorComponent extends React.Component {
constructor(props) {
super(props)
// business logic
let active = this.props.active
this.state = {
active : active
}
}
toggleActive = () => {
this.setState({
...this.state,
active : !this.state.active
})
}
// setup
componentDidMount() {
// here's where I tried to add a jQuery onclick listener to stop propagation, only to have the React Component listener get stopped
}
// teardown
componentWillUnmount() {
console.log("CustomizedIndicatorComponent destroyed!")
}
// the UI logic
render() {
if (this.state.active) {
return (
<div>
<div
className="badge badge-sm badge-info float-sm-left customized"
style={{marginRight:"10px"}}
>Customized</div>
<div
onClick={(e) => {
e.stopPropagation()
this.toggleActive()
}}
title="Click to undo customizations">
<i className="fa fa-undo" aria-hidden="true"></i>
</div>
</div>
)
}
return <div />
}
}
What happens when you run this?
When I run this, it renders. However, when I click the widget to "de-activate" the element, the container's event-handler still fires!!
I know there is a slew of internet questions about this issue or something close to it, but none of the ones I could find seem to be about this exact use case.
Also, adding an event listener in componentDidMount doesn't work, as that prevents anything from firing!
Is there any way I can make this work without wasting developer-hours refactoring everything including the parent HTMLElements?
A "hacky" way you may consider is to get the parent's id from inside the React component and disable the click event from there.
If id could not be passed as a property to the React component, you can try using ReactDOM.findDOMNode(this).parentNode.getAttribute("id") to get it and then disable the event using:
document.getElementById(id).style.pointerEvents = 'none';
In AngularJS 1.2, if I use a parent animation, the child animation doesn't work.
If I comment out app.animation('.parent', function () { .. }, then the child animation starts correctly.
How to get both parent and child animations working at the same time?
Plunker of my code
HTML:
<button ng-click="anim.toggleParent()">reveal parent</button>
<button ng-click="anim.toggleChild()">reveal child</button>
<div class="parent" ng-if="!anim.showParent">
<div class="child" ng-if="!anim.showChild">
</div>
</div>
JS:
app.animation('.parent', function () {
return {
// ...
};
});
// this doesn't work with parent animation =(
app.animation('.child', function () {
return {
// ...
};
});
Just insert ng-animate-children to the parent (Angular 1.2+).
<button ng-click="anim.toggleParent()">reveal parent</button>
<button ng-click="anim.toggleChild()">reveal child</button>
<div class="parent" ng-if="!anim.showParent" ng-animate-children>
<div class="child" ng-if="!anim.showChild">
</div>
</div>
Check the ngAnimate documentation:
Keep in mind that, by default, if an animation is running, any child elements cannot be animated until the parent element's animation has completed. This blocking feature can be overridden by placing the ng-animate-children attribute on a parent container tag.
<div class="slide-animation" ng-if="on" ng-animate-children>
<div class="fade-animation" ng-if="on">
<div class="explode-animation" ng-if="on">
...
</div>
</div>
</div>When the on expression value changes and an animation is triggered then each of the elements within will all animate without the block being applied to child elements.
Not sure whether this thread is closed. If so recommendation would be very helpful.
Facing the same issue here.
Angular animate has the below lines indicating that the child animations will not be triggered if parent has animation.
Not sure whether this is an issue or works as expected.
//skip the animation if animations are disabled, a parent is already being animated,
//the element is not currently attached to the document body or then completely close
//the animation if any matching animations are not found at all.
//NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
if (animationsDisabled(element, parentElement) || matches.length === 0) {
domOperation();
closeAnimation();
return;
}
Have raised a thread in Angular google group referenced the issue back here.
Also not sure if this thread is closed, but you could always edit the angular-animate.js file. Function animationsDisabled is where angular looks for the parent element to see if it will allow the child to animate. At the top of this function I added a check to see if the parent element has a class of animation-override (can be whatever you define). This way you can override the default functionality when needed.
function animationsDisabled(element, parentElement) {
if (parentElement[0].classList.contains('animation-override')) return false;
if (rootAnimateState.disabled) return true;
if(isMatchingElement(element, $rootElement)) {
return rootAnimateState.disabled || rootAnimateState.running;
}
do {
//the element did not reach the root element which means that it
//is not apart of the DOM. Therefore there is no reason to do
//any animations on it
if(parentElement.length === 0) break;
var isRoot = isMatchingElement(parentElement, $rootElement);
var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
var result = state && (!!state.disabled || !!state.running);
if(isRoot || result) {
return result;
}
if(isRoot) return true;
}
while(parentElement = parentElement.parent());
return true;
}
}]);