I have some code that unpacks an array and returns an array of JSX elements. This works fine, however, when one element is clicked, the "selectedEl" css class is removed from all other elements.
I'm pretty sure I've just made some stupid mistake but I can't seem to figure it out. Thanks
Code that unpacks the array and assigns onClick method:
function UnpackReccArray() {
const renderArray = []
for (let renderEl = 0; renderEl < self.state.capMethod; renderEl++) {
renderArray.push(
<span className="topicElement" onClick={self.pushToChosen.bind(this, self.state.reccDataQuery[renderEl].topicID, renderEl + "topicEl")} id={renderEl + "topicEl"}>
<p className="fontLibre">{self.state.reccDataQuery[renderEl].displayName}</p>
</span>
)
if (renderEl + 1 === self.state.capMethod) {
return (
<self.ResultRender title="Popular Subjects" renderContent={renderArray} />
)
}
}
}
Code that handles onClick function
pushToChosen = (id, elID) => {
const self = this
const localChoseArray = this.state.subjectChosenArray
const index = localChoseArray.indexOf(id.toString())
if (index > -1) {
localChoseArray.splice(index, 1);
self.setState({
subjectChosenArray: localChoseArray
}, () => {
document.getElementById(elID).classList.remove("selectedEl")
})
} else {
self.setState({
subjectChosenArray: [...this.state.subjectChosenArray, id.toString()]
}, () => {
document.getElementById(elID).classList.add("selectedEl")
})
}
document.getElementById(elID).classList.toggle("selectedEl")
}
GIF of the code in action
Thanks!
i didn't fully understand the code but it seems that the toggle is what causing the issue since the if and else handles all possible cases , toggle is probably going to do the opposite of the if else bloc .
Related
I recently tried to make a page that saves the input and displays it on the page. Everything works but i want to add a delete button at every input, which deletes only one input. I tried so many different ways, but none of them worked. If you have any idea how to solve this, I would be grateful.
Here is the code:
let savedinput = []
Localstoragesaves = localStorage.getItem("Zaznamki")
const Predpomnjenipodatki = JSON.parse(Localstoragesaves)
const DeleteButtonHTML = document.getElementById("izbrisi-gumb")
const userinput = document.getElementById("vnos-pr")
const inputsavebutton = document.getElementById("vnos-gumb")
const LabelHTML = document.getElementById("seznamzaznamkov")
const SaveTab = document.getElementById("zavihek-gumb")
const DeleteLast = document.getElementById("pocisti-zadnjo")
if (Localstoragesaves) {
savedinput = Predpomnjenipodatki
Render(savedinput)
}
DeleteLast.addEventListener('click', function(){
savedinput.pop()
Render(savedinput)
})
SaveTab.addEventListener('click',
function(){
browser.tabs.query({active: true, currentWindow: true}, function(tabs){
savedinput.push(tabs[0].url)
localStorage.setItem("Zaznamki", JSON.stringify(savedinput))
Render(savedinput)
console.log( localStorage.getItem("Zaznamki") )
})
})
function Render(parameter) {
let tabslabel = ""
for (i = 0; i < parameter.length; i++) {
tabslabel += `
<li>
<a href='${parameter[i]}' target='_blank'>${parameter[i]}</a>
</li>
`
}
LabelHTML.innerHTML = tabslabel
}
DeleteButtonHTML.addEventListener('dblclick',
function() {
localStorage.clear()
savedinput = []
LabelHTML.textContent = ''
})
inputsavebutton.addEventListener("click", function(){
const Vsebinavnosa = userinput.value
savedinput.push(Vsebinavnosa)
localStorage.setItem("Zaznamki", JSON.stringify(savedinput))
Render(savedinput)
userinput.value = ""
console.log( localStorage.getItem("Zaznamki") )
})
I solved this problem myself. I'm quite proud 😁. I want to thank you for your really quick responses even if I didnt get the solution here.
If someone has the same problem I had, I will tell you what I did.
In function render, I added a button with onclick function with argument, so that this new function which will be called will know which item from array needs to remove:
<button id="${parameter[i]}" onclick="Delete(${i})">Delete</button>
and then somewhere in this document I added this function which gets value from button click and knows which item from the array savedinput and localstorage needs to remove:
function Delete(parameter) {
mojizaznamki.splice(parameter, 1)
console.log(savedinput)
localStorage.setItem("Zaznamki", JSON.stringify(savedinput))
Render(savedinput)
}
Hope this helps others.
Greetings from Slovenia
No matter what I try, the .onclick or addEventListener 'click' will not work on my dynamically created buttons and I can't figure out why. As I was looking for solutions, I came across Event Delegation and I looked through 3 different websites and looked at the examples. I was sure this was going to solve my problem and I tried to mimic the examples but still it isn't working. I posted a question on here earlier but it was immediately removed because apparently it was too similar to another question (that was 12 years old!) but when I looked at that question they were using jQuery. I'm still a beginner in JS so I would prefer to understand how to resolve this in plain JS and I'm hoping this won't be removed.
This is my code:
document.addEventListener('DOMContentLoaded', function() {
userData();
document.querySelector('.list-group').addEventListener('click', function(e) {
if(e.target && e.target.nodeName == "BUTTON"){
console.log(e.target.id);
}
});
})
function userData() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(users => {
const h6 = document.createElement("h6");
h6.innerText = "List of Users";
const userList = document.createElement("div");
userList.className = "list-group";
users.forEach(function(user) {
const userButton = document.createElement("button");
userButton.className = "list-group-item list-group-item-action";
userButton.id = `${user.id}`;
userButton.innerHTML = `
<strong>${user.name}</strong><br>
${user.email}<br>
${user.address.city}<br>
`;
userList.appendChild(userButton);
});
const container = document.querySelector('#response');
container.appendChild(h6);
container.insertBefore(userList, h6.nextSibling);
});
}
function userSelect(user_id) {
fetch(`https://jsonplaceholder.typicode.com/users/${user_id}`)
.then(response => response.json())
.then(user => {
console.log(user);
});
}
What I have now is a list of users and ultimately I want to be able to click on a user and bring up the full details of that user. At first I was trying to use the onclick function to redirect to the userSelect function but when that failed I looked around and found Event Delegation and still no luck. I tried to move the document.querySelector('.list-group) section down at the end of the userData function and still no luck. When I click on a button nothing shows up in console, if I use the userSelect function directly in console a user object appears. I'm at a real loss on how to get this to work. Please help!
Since function userData is making asynchronous call, the issue seems to be that you are adding the click event handler before the element with class '.list-group' got created.
You should use something like this to add click handler
document.addEventListener('DOMContentLoaded', function () {
userData().then(response => {
document.querySelector('.list-group').addEventListener('click', function (e) {
if (e.target && e.target.nodeName == "BUTTON") {
console.log(e.target.id);
}
})
});
})
Try below snippet:
document.addEventListener('DOMContentLoaded', function() {
userData().then(response => {
document.querySelector('.list-group').addEventListener('click', function(e) {
if (e.target && e.target.nodeName == "BUTTON") {
console.log(e.target.id);
}
})
});
})
function userData() {
return fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(users => {
const h6 = document.createElement("h6");
h6.innerText = "List of Users";
const userList = document.createElement("div");
userList.className = "list-group";
users.forEach(function(user) {
const userButton = document.createElement("button");
userButton.className = "list-group-item list-group-item-action";
userButton.id = `${user.id}`;
userButton.innerHTML = `
<strong>${user.name}</strong><br>
${user.email}<br>
${user.address.city}<br>
`;
userList.appendChild(userButton);
});
const container = document.querySelector('#response');
container.appendChild(h6);
container.insertBefore(userList, h6.nextSibling);
});
}
<div id="response">
</div>
or you can move the addEventListener code to end of userData
At first run the program is working correctly.
But after clicking on the sum or minus button the function will not run.
componentDidMount() {
if(CONST.INTRO) {
this.showIntro(); // show popup with next and prev btn
let plus = document.querySelector('.introStep-plus');
let minus = document.querySelector('.introStep-minus');
if (plus || minus) {
plus.addEventListener('click', () => {
let next = document.querySelector(CONST.INTRO[this.state.introStep].element);
if (next) {
next.parentNode.removeChild(next);
}
this.setState({
introStep: this.state.introStep + 1
});
this.showIntro();
});
}
}
As referenced in React documentation: refs and the dom the proper way to reference DOM elements is by using react.creatRef() method, and even with this the documentation suggest to not over use it:
Avoid using refs for anything that can be done declaratively.
I suggest that you manage your .introStep-plus and introStep-minus DOM element in your react components render method, with conditional rendering, and maintain a state to show and hide .introStep-plus DOM element instead of removing it inside native javascript addEventListener click
Here is a suggested modification, it might not work directly since you didn't provide more context and how you implemented the whole component.
constructor() {
...
this.state = {
...
showNext: 1,
...
}
...
this.handlePlusClick = this.handlePlusClick.bind(this);
}
componentDidMount() {
if(CONST.INTRO) {
this.showIntro(); // show popup with next and prev btn
}
}
handlePlusClick(e) {
this.setState({
introStep: this.state.introStep + 1,
showNext: 0,
});
this.showIntro();
});
render() {
...
<div className="introStep-plus" onClick={this.handlePlusClick}></div>
<div className="introStep-minus"></div>
{(this.stat.showNext) ? (<div> NEXT </div>) : <React.fragment />}
...
}
I am trying to add an onClick event handler to objects in an array where the class of a clicked object is changed, but instead of only changing one element's class, it changes the classes of all the elements.
How can I get the function to work on only one section element at a time?
class Tiles extends React.Component {
constructor(props) {
super(props);
this.state = {
clicked: false,
content : []
};
this.onClicked = this.onClicked.bind(this);
componentDidMount() {
let url = '';
let request = new Request(url, {
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json'
})
});
fetch(request)
.then(response => response.json())
.then(data => {
this.setState({
content : data
})
} );
}
onClicked() {
this.setState({
clicked: !this.state.clicked
});
}
render() {
let tileClass = 'tile-content';
if (this.state.clicked) {
tileClass = tileClass + ' active'
}
return (
<div className = 'main-content'>
{this.state.pages.map((item) =>
<section key = {item.id} className = {tileClass} onClick = {this.onClicked}>
<h4>{item.description}</h4>
</section>)}
<br />
</div>
)
}
class App extends React.Component {
render() {
return (
<div>
<Tiles />
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('content-app'))
You have onClicked() define in your 'main-content' class. So that's where it fires.
constructor(props) {
// super, etc., code
this.onClicked = this.onClicked.bind(this); // Remove this line.
}
Remove that part.
You can keep the onClicked() function where it is. Your call in render() is incorrect, though: onClick = {this.onClicked}>. That accesses the onClicked ATTRIBUTE, not the onClicked FUNCTION, it should be this.onClicked().
Let me cleanup your call in render() a little bit:
render() {
let tileClass = 'tile-content';
return (
<div className = 'main-content'>
// some stuff
<section
key={item.id}
className={tileClass}
onClick={() => this.onClicked()} // Don't call bind here.
>
<h4>{item.description}</h4>
</section>
// some other stuff
</div>
)
}
It is happening for you, because you are assigning active class to all sections once user clicked on one of them. You need somehow to remember where user clicked. So I suggest you to use array, where you will store indexes of all clicked sections. In this case your state.clicked is an array now.
onClicked(number) {
let clicked = Object.assign([], this.state.clicked);
let index = clicked.indexOf(number);
if(index !== -1) clicked.splice(index, 1);
else clicked.push(number)
this.setState({
clicked: clicked
});
}
render() {
let tileClass = 'tile-content';
return (
<div className = 'main-content'>
{this.state.pages.map((item, i) => {
let tileClass = 'tile-content';
if(this.state.clicked.includes(i)) tile-content += ' active';
return (
<section key = {item.id} className = {tileClass} onClick = {this.onClicked.bind(this, i)}>
<h4>{item.description}</h4>
</section>
)
})}
<br />
</div>
)
}
StackOverflow does a particularly poor job of code in comments, so here's the implementation of onClicked from #Taras Danylyuk using the callback version of setState to avoid timing issues:
onClicked(number) {
this.setState((oldState) => {
let clicked = Object.assign([], this.state.clicked);
let index = clicked.indexOf(number);
if(index !== -1) {
clicked.splice(index, 1);
} else {
clicked.push(number);
}
return { clicked };
});
}
The reason you need this is because you are modifying your new state based on the old state. React doesn't guarantee your state is synchronously updated, and so you need to use a callback function to make that guarantee.
state.pages need to keep track of the individual click states, rather than an instance-wide clicked state
your onClick handler should accept an index, clone state.pages and splice your new page state where the outdated one used to be
you can also add data-index to your element, then check onClick (e) { e.currentTarget.dataset.index } to know which page needs to toggle clickstate
I'm stumped with this one and would really appreciate someone's help.
I'm customizing highslide for integration with wordpress. Via the following code within the highslide.config.js file I'm adding a class name to certain elements and passing different attributes through an onClick call depending on certain conditions.
Everything works until I add the following code:
if(hsGroupByWpGallery){
slideshowGroup: this.parentNode.parentNode.parentNode.id
};
When the above code is present, not only does that one statement not execute, but the whole thing stops working. Even if the if statement is something like if(1=1){}; it still breaks.
If I have instead simply slideshowGroup: this.parentNode.parentNode.parentNode.id or nothing (the two options I'm looking for), both do what I would expect. I just need an if statement to switch between them.
Here's the relevant code:
jQuery(document).ready(function() {
var hsCustomGalleryGroupClass = 'fbbHighslide_GalleryGroup';
var hsCustomGalleryGroupChecker = 0;
var hsGroupByWpGallery = true;
jQuery('.' + hsCustomGalleryGroupClass).each(function(){
hsCustomGalleryGroupChecker++;
return false;
});
if (hsCustomGalleryGroupChecker > 0){
jQuery('.' + hsCustomGalleryGroupClass).each(function(i, $item) {
var grpID = $item.id;
jQuery('#' + grpID + ' .gallery-item a').addClass('highslide').each(function() {
this.onclick = function() {
return hs.expand(this, {
slideshowGroup: grpID
});
};
});
});
} else {
jQuery('.gallery-item a').addClass('highslide').each(function() {
this.onclick = function() {
return hs.expand(this, {
// This is the problem if statement
if(hsGroupByWpGallery){
slideshowGroup: this.parentNode.parentNode.parentNode.id
};
});
};
});
};
});
Thanks in advance.
The problem is you are trying to assign a conditional property.. you can't have a if condition inside a object definition like that
jQuery('.gallery-item a').addClass('highslide').each(function () {
this.onclick = function () {
var obj = {};
//assign the property only if the condition is tru
if (hsGroupByWpGallery) {
obj.slideshowGroup = this.parentNode.parentNode.parentNode.id;
}
return hs.expand(this, obj);
};
});
Another way to do the same is
jQuery('.gallery-item a').addClass('highslide').each(function () {
this.onclick = function () {
//if the flag is true sent an object with the property else an empty object
return hs.expand(this, hsGroupByWpGallery ? {
slideshowGroup: this.parentNode.parentNode.parentNode.id
} : {});
};
});
I think you might want this, based on the other code:
jQuery('.gallery-item a').addClass('highslide').each(function() {
this.onclick = function() {
if(hsGroupByWpGallery){
return hs.expand(this, {
slideshowGroup: this.parentNode.parentNode.parentNode.id
});
}
};
});