Odd Behavior With JavaScript OnClick Event - javascript

I have a React app which has this code:
<div className={ mainCss } data-appmode={ AppMode.MAIN } onClick={ this.handleAppModeClick.bind(this) }>
<div className="NavLinkIconContainer"></div>
<div>Main</div>
</div>
When I click on div with className="NavLinkIconContainer" the handleAppModeClick function does not work correctly. The function fires but does not pick up the data-* attribute. However, when I click on the div with Main as it's content it works perfectly picking up the data-* attribute. Why is this and how do I fix it?

You can the data-appmode value from event.currentTarget.dataset.
event - the event object
currentTarget - the element with the onClick
dataset - an easy access to data-* values
class Demo extends React.Component {
handleAppModeClick = event => console.log(event.currentTarget.dataset.appmode);
render() {
return (
<div
data-appmode="example"
onClick={this.handleAppModeClick}>
<div className="NavLinkIconContainer">NavLinkIconContainer</div>
<div>Main</div>
</div>
)
}
}
ReactDOM.render(
<Demo />,
root
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Ori Drori's suggestion will work. Alternatively, you can write your component as
<div className={ mainCss } data-appmode={ AppMode.MAIN } onClick={ ()=>this.handleAppModeClick(AppMode.MAIN) }>
<div className="NavLinkIconContainer"></div>
<div>Main</div>
This will fire off the function with a hard-coded argument. In my opinion, it's a little bit cleaner than using event.currentTarget.dataset. That being said, I am sure that others disagree with me, and if the solution works, it works.
Alternatively, you can use
handleAppModeClick = event => console.log(event.target.getAttribute('data-appmode'));
to get the data from the actual element.

Related

setFunction of useState won't change OnClick

I have this useState element, but the function doesn't get called onClick.
I have tried many solutions and debugging and it seems like the Click event doesn't get called, no matter what.
const [modalHotel, setModalHotel] = useState(false)
{modalHotel && <ModalHotel CloseModal = {setModalHotel} />}
<img src="./assets/square.svg" alt="expand" onClick={() => setModalHotel(!modalHotel)}/>
Sometimes elements won't register onClick events in React unless you specify a tabIndex. Try this:
const checkClick = () => {
console.log("It worked!!");
setModalHotel(!modalHotel);
}
<img tabIndex={0} src="./assets/square.svg" alt="expand" onClick={checkClick} />
this will help you to debug whether the click event is actually being fired.
Side Note:
From an accessibility perspective, it's almost always preferrable to use either button or a elements to handle clicks like this. They have tabIndexes by default, and better a11y support in general.
Found the problem!
I had this z-index: -1; in my css, removed it and it worked.
You haven't really provided enough information to completely diagnose your issue. But in the meantime, here is a working snippet with an element that is clickable (a button) to toggle state using a useState hook. You can compare what is different between your non-working code and this working example.
const { useState } = React;
const Thing = (props) => {
const [modalHotel, setModalHotel] = useState(false);
return (
<div>
<h1>{modalHotel.toString()}</h1>
<button onClick={() => setModalHotel(!modalHotel)}>Toggle</button>
</div>
);
}
ReactDOM.render(
<Thing />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Is there any way to add events to all children in ReactJS?

Is there any way to add all the events from the parent element to all the child elements using ReactJS without hard coding all of the events?
<div className='myClass' onClick={handleSelect} onDoubleClick={handleOpen}>
<span className='child' />
</div>
Click handlers propagate, so there's no need. Attach a single handler to the parent.
const App = () => {
const handleSelect = () => console.log('click');
const handleOpen = () => console.log('double click');
return (
<div className='myClass' onClick={handleSelect} onDoubleClick={handleOpen}>
<span className='child'>child</span>
</div>
);
};
ReactDOM.render(<App />, document.querySelector('.react'));
<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 class='react'></div>
If you need to identify which child element was clicked on, or exclude certain children from resulting in the handler running, use the event passed to the handler and see if its target .matches what you're looking for.

How to remove added classes from an element in reactjs

I am making a drag n drop sort of game where you match the logos with their corresponding name.
If user matches the logo with the name correctly than the field which you could drop the logo gets additional classes.
Like this:
if (isCorrectMatching) {
event.target.classList.add("dropped");
draggableElement.classList.add("dragged");
event.target.classList.add("dragged");
event.target.setAttribute("draggable", "false");
draggableElement.setAttribute("draggable", "false");
event.target.innerHTML = `<i class="fab fa-${draggableElementBrand}" style="color: ${draggableElement.style.color};"></i>`;
}
If every match is found user can go to next level , my problem is that these additional classes are staying there , how do I remove them ?
I am mapping them out like this:
<div className="containerItems">
{draggableItems.map((x, i) => {
return (
<div className="draggable-items">
<i
onDragStart={(e) => dragStart(e)}
className={`draggable fab fa-${x}`}
id={x}
draggable="true"
ref={draggableOnes.current[i]}
></i>
</div>
);
})}
</div>;
{
matchingPairs.map((x, i) => {
return (
<section className="matching-pairs">
<div className="matching-pair">
<span className="label">{x}</span>
<span
className="droppable"
// ref={droppableOnes.current[i]}
onDragEnter={(e) => dragEnter(e)}
onDragOver={(e) => dragOver(e)}
onDragLeave={(e) => dragLeave(e)}
onDrop={(e) => drop(e)}
data-brand={x}
></span>
</div>
</section>
);
});
}
I can not seem to solve this one, like how do I remove all the classes that I've added when there was a correct matching.
I would like to remove basically everything that I've added in my if (isCorrectMatching) .
I've tried to use refs but it did not work. What is the way to go for this?
In React, you don't directly manipulate DOM elements (well, almost never), including their the class lists. Instead, you keep your state information in the component and use that state information to render the elements that make up your component (including their classes). React will then compare the rendered elements you return with the DOM and make any necessary changes (such as updating the classList). So in your code, when you see that you have a correct matching, you wouldn't directly modify those DOM elements' classList lists, you'd update your state to remember the match, and use that state information in the next render to put the appropriate classes on the elements being rendered.
Here's a simpler example with a tickbox, but it's the same general concept:
const {useState} = React;
const Example = () => {
const [isChecked, setIsChecked] = useState(false);
return <div>
<label>
<input
type="checkbox"
checked={isChecked}
onChange={() => setIsChecked(flag => !flag)}
/>
Ticked
</label>
<div className={isChecked ? "yes" : "no"}>
Example
</div>
</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
.yes {
color: green;
}
.no {
color: #d00;
}
label {
user-select: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Note how the state member isChecked determines what classes the div has, and is updated by ticking/unticking the checkbox.

document.getElementById for iFrame in ReactJS

In my site, I need to send information (via postMessage) to an iFrame. I know in regular Javascript, I would accomplish this by using document.getElementById or $("#iframe") in JQuery to select the iframe. However, I am unsure of how to do this in ReactJS. Is there a specific way of doing this in ReactJS/NextJS that I just don't know about? I need access to the iframe (the child component) from its container (parent component).
If the iframe is rendered by React, and only the component that renders it (or its descendants) needs to access it, then typically you use refs.
If the iframe is always on the page, or rendered in some way outside of React, it's perfectly fine to get it via document.getElementById, document.querySelector, or other DOM methods.
Here's an example of using a ref in a functional component via useRef, but you can do the same thing (in a different way) in a class component via createRef. I'll use a div instead of an iframe, but it's the same for iframes.
function Example() {
const ref = React.useRef(null);
const doSomething = () => {
if (ref.current) {
console.log(`The div's text is "${ref.current.textContent}"`);
} else {
console.log("The div doesn't exist");
}
};
return (
<div>
<div ref={ref}>
This is the target div
</div>
<input type="button" onClick={doSomething} value="Click Me" />
</div>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
Class component example:
class Example extends React.Component {
constructor(props) {
super(props);
this.ref = React.createRef();
this.doSomething = this.doSomething.bind(this);
}
doSomething() {
if (this.ref.current) {
console.log(`The div's text is "${this.ref.current.textContent}"`);
} else {
console.log("The div doesn't exist");
}
}
render() {
return (
<div>
<div ref={this.ref}>
This is the target div
</div>
<input type="button" onClick={this.doSomething} value="Click Me" />
</div>
);
}
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

Toggle prop passed on one of many targets in ReactJS

Just starting off with ReactJS and have a project where I am showing an accordion of issues and including a details area that is hidden on the start.
There is a button in the accordion bar that should pass a prop to the child element to hide or show them. I have refs on the button and on the details child compoment and added a function to call the function and pass the ref of the details area. I am just not sure how to dynamically change the class hidden on one of many areas and not all of them.
Not sure if putting a class on each element and then learning how to toggle the particular child's class is better or changing the prop to the child.
I can get to the change function but am drawing a blank from there and all the googling shows how to do one element with a grand change of state but I need individual elements.
Here is what I have so far.
Parent
...
<AccordionItem key={item.id} className={iconClass} title={`${item.area}`} expanded={item === 1}>
{
item.issues.map(issue => {
let trim = (issue.issue.length>21) ? `${issue.issue.substring(0,22)}...`: issue.issue;
return (
<div className="issue-bar container-fluid">
<div className="row issue-bar-row">
<span className="issue-title"><img src={CriticalRed} alt="Critical"/> {trim}</span>
<span className="btns">
<button className="btn btn-details" onClick={() => this.showDetail(`details-${issue.id}`)}>Details</button>
</span>
</div>
<IssuesDetails ref={`details-${issue.id}`} issue={issue} shouldHide={true} />
</div>
)
})
}
<div>
</div>
</AccordionItem>
...
Child
export default class IssuesDetails extends Component{
render(){
let issueDetails = classNames( 'issue-details', { hidden: this.props.shouldHide } )
return(
<div className={issueDetails}>
<div className="issues-details-title">
<h3>{this.props.issue.issue}</h3>
</div>
<div className="issues-details-details">
{this.props.issue.details}
</div>
<div className="issues-details-gallery">
<ImageGallery source={this.props.issue.photos} showPlayButton={false} useBrowserFullscreen={false} />
</div>
<button className="btn btn-success">Resolve</button>
</div>
)
}
}
Thanks for any help you provide or places you can send me!
If i'm understanding correctly, you need to be able to swap out shouldHide={true} in certain circumstances. To do this, you'll want your parent component to have a state object which indicates whether they should be hidden or not.
Exactly what this state object looks like depends on what sort of data you're working with. If the issues is a single array, then perhaps the state could be an array of booleans indicating whether each issue is expanded or not. I suspect you may have a more nested data structure, but i can't tell exactly since some of the code was omitted.
So assuming you have an array, it might look like this (i've omitted some things from the render method for brevity):
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
hidden: (new Array(props.issues.length)).fill(false),
};
}
showDetail(index) {
let newHidden = this.state.hidden.slice();
newHidden[index] = true;
this.setState({
hidden: newHidden
});
}
render() {
return (
<AccordionItem>
{this.props.issues.map((issue, index) => {
<div>
<button onClick={() => this.showDetail(index))}/>
<IssuesDetails issue={issue} shouldHide={this.state.hidden[index]}/>
</div>
})}
</AccordionItem>
);
}
}
Take a look at these:
https://codepen.io/JanickFischr/pen/xWEZOG
style={{display: this.props.display}}
I think it will help with your problem. If you need more information, please just ask.

Categories

Resources