React OnClick for item not working - javascript

I have a react component which renders a list item with individual OnClick.
In order to find out which item was clicked, the handler accepts a parameter. The handler does get invoked - but no matter which item is clicked - console always logs item3 (as if item3 is clicked). What am I doing wrong here?
class Item {
constructor(props) {
super(props);
this.onItemClickHandler = this.onItemClickHandler.bind(this)
}
onItemClickHandler (itemName) {
console.log("Clicked " + itemName)
}
render() {
this.items = ["item1", "item2", "item3"]
var lis = []
for (var liName in this.items) {
var liName2 = this.items[liName]
console.log("Adding " + this.items[liName])
lis.push(<li className="item-ListItem" key={this.items[liName]} onClick={() => this.onItemClickHandler(this.items[liName])}><span>{this.items[liName]}</span></li>)
}
return (
<div className="item">
<label className="item-Header"><u>items</u></label>
<ul className="item-List">
{lis}
</ul>
</div>
);
}
This line:
onClick={() => this.onItemClickHandler(this.items[liName])}>
appears to be correct.

The issue is that you are not capturing the value of this.items[liName] correctly because by the time you reach the third item iteration the onClick handler will always have the value of this.items[liName] set to the third item.
The solution for that is using closure to capture the value correctly, i edited your code and created a fully working example in this link
https://codesandbox.io/s/3xrp6k9yvp
Also the example code is written below with the solution
class App extends Component {
constructor(props) {
super(props);
this.onItemClickHandler = this.onItemClickHandler.bind(this);
}
onItemClickHandler(itemName) {
console.log("Clicked " + itemName);
}
render() {
this.items = ["item1", "item2", "item3"];
var lis = [];
for (var liName in this.items) {
var liName2 = this.items[liName];
console.log("Adding " + this.items[liName]);
//the clickHandler function here is the solution we created a function that get executed immediately each iteration and return a new function that has the correct value of `this.items[liName]` saved
var clickHandler = (item => {
return event => {
this.onItemClickHandler(item);
};
})(this.items[liName]);
lis.push(
<li
className="item-ListItem"
key={this.items[liName]}
onClick={clickHandler} // here we use the clickHandler function directly
>
<span>
{this.items[liName]}
</span>
</li>
);
}
return (
<div className="item">
<label className="item-Header">
<u>items</u>
</label>
<ul className="item-List">{lis}</ul>
</div>
);
}
}
For more info and examples about closures check this link
Edit We can use let in ES6 instead of var in our for loop as mentioned by #ArchNoob because using let will make the liName block scoped

Please take care of indentation while posting the code. Its very difficult to understand without that. You have to make use of closure. whenever the loop gets over liName variable gets set to last index as scope chain will keep the liName value to last one. The solution is not make a new scope between handler and click handler function where it is callled.
Here is the solution:
class Test extends React.Component {
constructor(props) {
super(props)
this.onItemClickHandler =
this.onItemClickHandler.bind(this)
}
onItemClickHandler(itemName) {
debugger
console.log("Clicked " + itemName)
}
render() {
this.items = ["item1", "item2", "item3"]
var lis = []
for (var liName in this.items) {
var liName2 = this.items[liName]
console.log("Adding " + this.items[liName])
debugger
lis.push( <li className = "item-ListItem"
key = {
this.items[liName]
}
onClick = {
((item) => {
return () => this.onItemClickHandler(item)
})(this.items[liName])
}
>
<span>
{this.items[liName]}
</span>
</li>
)
}
return (
<div>
<div className="item">
<label className="item-Header">
<u>items</u>
</label>
<ul className="item-List" >
{lis}
</ul>
</div>
</div>
)
}
}
ReactDOM.render(<Test />, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

I will do it the recommended way.
First make a separate component of list-item.
To render list in react Js
//Handler
clickHandler=(id)=>{
console.log(`Clicked on Item with Id ${id}`);
}
//render method
render(){
let items = null;
if(this.state.isAnyItem){
<div className="items-list">
{
this.state.items.map((item)=>{
<Item key={item.id} item={item} click={(item.id)=>this.clickHandler(item.id)}/>
})
}
</div>
}
return (
<div>
{/*Some JSX here*/}
{items}
</div>
)
}
And Now the Item Component like
<div onClick={props.click}>
{/*Some JSX Here*/}
<h3>{item.name}</h3>
</div>

Related

Can't understand this behaviour

I have a simple notes app and delete isn't working properly, even though state is correctly updated.
The state is being updated correctly as I can see on the console.
But all the notes including and after that note that I click delete on are getting deleted on the DOM for some reason.
For example if I have 3 notes ["hello","hi","hey"], if I delete the second note("hi"), the state shows the correct notes ["hello","hey"] but both "hi" and "hey" are deleted on the page not just "hi" like it was supposed to.
I can't understand where I've gone wrong, so I'd like to correct it.
App.js:
handleDelete = (note_id) => {
const id = 'display-' + note_id;
console.log(id);
document.getElementById(id).style.display = 'none';//remove element
//delete the note and update state
const newNotesList = this.state.notesList;
newNotesList.splice(note_id,1);
this.setState({
notesList : newNotesList
})
console.log(this.state.notesList)
}
Display.js:
render(){
const notesList = this.props.notesList;
const displayNotes = notesList.map( (note,note_id) =>
<div id={ 'display-' + note_id } className="display">
{/*some code*/}
<button type="button" className="delete-button"
onClick = { () => this.props.handleDelete(note_id) } > Delete </button>
</div> );
return <div>{displayNotes}</div>;
}
do like this
// don't mutation object
// App.js
handleDelete = (note_id) => {
//delete the note and update state
const newNotesList = this.state.notesList.filter((item, index)=> index !== note_id)
this.setState({
notesList : newNotesList
})
}
// Display.js
render(){
const notesList = this.props.notesList;
const displayNotes = notesList.map( (note,note_id) =>
<div>
{/*some code*/}
<button type="button" className="delete-button"
onClick = { () => this.props.handleDelete(note_id) } > Delete </button>
</div> );
return <div>{displayNotes}</div>;
}
==== here is the reason ========
at first the state.note is ["hello","hi","hey"], in the function of handleDelete you delete "hi" and make the id of dispaly-1's display become to hidden, so when react render the state.note === ["hello","hey"] the element of "hey"'s id become dispaly-1 so "hey" will be hidden. you will only see "hello"
handleDelete = (note_id) => {
// this.state.notesList === ["hello","hi","hey"]
const id = 'display-' + note_id;
console.log(id);
// the problem is u hidden the next element
// 1. newNotesList.splice(note_id,1);
// 2. document.getElementById(id).style.display = 'none'
// two methods choose one or you will hidden two elements
document.getElementById(id).style.display = 'none';//remove element
//delete the note and update state
const newNotesList = this.state.notesList;
newNotesList.splice(note_id,1);
this.setState({
notesList : newNotesList
})
console.log(this.state.notesList)
// for example
// the `this.state.notesList` new is ["hello","hey"]
}
notesList.map( (note,note_id) =>
// `note` is ["hello","hey"] , u don't need hidden the `2rd` element
//you have been delete 'hi' ` the id of `display-1`'s display ==='hidden'
// now "hey"'s id is `display-1`
<div id={ 'display-' + note_id } className="display">
{/*some code*/}
<button type="button" className="delete-button"
onClick = { () => this.props.handleDelete(note_id) } > Delete </button>
</div> );
``

Why console says my keys are not unique?

My console says that all keys are not unique, and that's why my removeItem function doesn't work. I'm trying to make a to-do list with React.js, and now I'm adding remove button. Can you help me? Here's the code:
var TodoItems = React.createClass({ //This is the removeItem function, that takes the key as a parameter and compares i.key to parameter.
removeItem: function(key){
var itemArray = this.props.entries;
for (var i = 0; i < itemArray.length; i++)
if (itemArray[i.key] === key) {
itemArray.splice(i, 1);
break;
}
},
render: function() {
var todoEntries = this.props.entries;
var _removeItem = this.removeItem;
function createTasks(item) {
return (
<div>
<li key={item.key}>{item.text}</li>
<button onClick = {_removeItem(item.key)} className= "remove"> Remove </button>
</div>
);
}
var listItems = todoEntries.map(createTasks);
return (
<ul className="theList">
{listItems}
</ul>
);
}
});
var TodoList = React.createClass({
getInitialState: function() {
return {
items: []
};
},
addItem: function(e) {
var itemArray = this.state.items;
//Here I create the key:
itemArray.push(
{
text: this._inputElement.value,
key: Math.random().toString(36).substring(7)
}
);
this.setState({
items: itemArray
});
this._inputElement.value = "";
e.preventDefault();
},
render: function() {
return (
<div className="todoListMain">
<div className="header">
<form onSubmit = {this.addItem}>
<input ref={(a) => this._inputElement = a}
placeholder="enter task">
</input>
<button type="submit">add</button>
</form>
</div>
<TodoItems entries={this.state.items}/>
</div>
);
}
});
The problem lies within your createTasks function.
function createTasks(item) {
return (
<div>
<li key={item.key}>{item.text}</li>
<button onClick = {_removeItem(item.key)} className= "remove"> Remove </button>
</div>
);
}
You're return an array of divs to populate an unordered list. Firstly, you should be populating any ul element with li, and any content within that li element.
The reason reactjs is giving you can error is because you're adding your key to a child of the element you're returning instead of the root node, in this case a div.
Also, your removeItem function isn't working because it looks like it's being invoked when you're building each task, I've edited my answer to resolve this.
The following should work without issue:
function createTasks(item) {
return (
<li key={item.key}>
{item.text}
<button onClick = {this.removeItem.bind(this, item.key)} className= "remove"> Remove </button>
</li>
);
}
Edited: I misread a portion of the question, and have edited my answer.

onClick event returning Every Modal instead of a Single Modal

I'm trying to build a Jeopardy like game using React and Redux. I currently have an onClick event set to each li, but whenever I click on it, I get every Modal to pop up instead of the one that is attached to that li item. I have my code separated in different files but I believe these two files are the only ones I need to show:
const ModalView = React.createClass({
pass: function(){
console.log('pass clicked');
store.dispatch({type:"MODAL_TOGGLE"})
},
submit: function(){
console.log('submit clicked');
store.dispatch({type:"MODAL_TOGGLE"})
},
render: function(){
let question = this.props.question
let category = this.props.category
let answer = this.props.answer
let val = this.props.value
return (
<div>
<div className="modal">
<p>{category}</p>
<p>{question}</p>
<p>{answer}</p>
<p>{val}</p>
<input
type="text"
placeholder="type in your answer">
</input>
<button onClick={this.submit}>Submit</button>
<button onClick={this.pass}>Pass</button>
</div>
</div>
)
}
})
and ValuesView
const ValuesView = React.createClass({
modalPopUp: function(value){
store.dispatch({type:"MODAL_TOGGLE"})
},
render: function(){
let showClass = "show-content"
let hideClass = "hide-content"
if (this.props.modal){
showClass = "hide-content"
hideClass = "show-content"
}
return (<div>
<ul className="list">
{this.props.datum.clues.slice(0,5).map((data, i) => {
if (data.value === null){
return <div>
<div className={hideClass}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value} />
</div>
<li onClick={this.modalPopUp} key={i}>$600</li>
</div>
}
return <div>
<div className={hideClass}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value}/>
</div>
<li
category = {this.props.category}
onClick={this.modalPopUp} key={i}>${data.value}</li>
</div>
})}
</ul>
</div>
)
}
})
How would I go about only getting the corresponding Modal to display instead of every one? Thanks!!
If you just want to code real Modal I suggest you to use some already implemented component like https://github.com/reactjs/react-modal (I'm not saying is mandatory, nor even whit the example I suggest, could be other)
I think that store.dispatch({type:"MODAL_TOGGLE"}) in some way toggle modal prop between true and false. Then you just use that flag to toggle a class to show or hide content I guess (I would need to see you css).
The problem with this approach is (apart that is not the best way to do this for many reasons) that you are using the same class for every item in your clues array.
In some way you need to store which is the "modal" you want to show, and then in the render, just apply the show class to this item.
Maybe:
const ValuesView = React.createClass({
modalPopUp: function(index){
return function (event) {
store.dispatch({
type:"MODAL_TOGGLE",
payload: {
modalIndex: index // Save modalIndex prop
}
})
}
},
render: function(){
return (<div>
<ul className="list">
{this.props.datum.clues.slice(0,5).map((data, i) => {
if (data.value === null){
return <div>
<div className={(this.prosp.modal && this.props.modalIndex === i) ? "show-content" : "hide-content"}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value} />
</div>
<li onClick={this.modalPopUp(i)} key={i}>$600</li>
</div>
}
return <div>
<div className={(this.prosp.modal && this.props.modalIndex === i) ? "show-content" : "hide-content"}>
<ModalView
category = {this.props.category}
question = {data.question}
answer = {data.answer}
value ={data.value}/>
</div>
<li
category = {this.props.category}
onClick={this.modalPopUp(i)} key={i}>${data.value}</li>
</div>
})}
</ul>
</div>
)
}
})

reactjs - Function not working inside a map function

I have a component to display list of tags, and the user can select tags to follow them. The tags are displaying fine. I would like to get the selected tag and store it inside a new array tagsSelectedList. So, when the user clicks a Tag, I would like to get that tag and push it to tagsSelectedList. However I am getting an error after I placed an onClick inside the li of the map function.
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
This is the error:
Uncaught TypeError: Cannot read property 'selectTag' of undefined
Component.js:
let tags = [
{id: "1", tagName: "Arts"},
...
...
{id: "59", tagName: "Writing"}
}];
var tagsSelectedList = [];
export default class SignUpSubscribeTags extends React.Component {
constructor(props){
super(props);
}
selectTag = (e) => {
console.log(e.target.id);
}
render() {
let tagList = tags.map(function(Tag){
var i = 0;
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
});
return(
<div id="signup-process-wrapper-addTags">
<div id="add_tags">
<ul id="tag_list">
{tagList}
</ul>
</div>
</div>
);
}
}
However if I remove onClick={this.selectTag} from the return statement of tagList,
<li id={Tag.tagName} class="tag" key={Tag.id}>{Tag.tagName}</li>
and place a li with an onClick={this.selectTag} inside the ul,
<ul id="tag_list">
<li id="tagName" class="tag" onClick={this.selectTag}>tagName</li>
{tagList}
</ul>
it works fine! I get no error.
What am I doing wrong? Please help me. Thank you.
You need to scope this so it references the React component context
There are a couple of ways to do this:
Option 1:
Using bind()
render() {
let tagList = tags.map(function(Tag){
var i = 0;
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
}.bind(this));
return(
<div id="signup-process-wrapper-addTags">
<div id="add_tags">
<ul id="tag_list">
{tagList}
</ul>
</div>
</div>
);
}
Option 2:
You can use the ES6 arrow function to scope it
let tagList = tags.map((Tag) => {
var i = 0;
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
});
Option 3:
The map function also takes a second argument that specifies what this refers to
let tagList = tags.map(function(Tag) {
var i = 0;
return (
<li id={Tag.tagName} class="tag" key={Tag.id} onClick={this.selectTag}>{Tag.tagName}</li>
);
}, this);

Assign value to variable unique to div, and pass varaible to Component function In React

Below is a React.Component running on Meteor.js, hence the Session functions which are global.
For each div inside div#sidebar-wrapper, the same value foo or bar is passed into the methods this.isActive and this.changeTab.
<div
className={this.isActive('foo') + " tab-btn"}
onClick={this.changeTab.bind(null, 'foo')}>
<span className="title">Foo</span>
</div>
Question: Is it possible to assign this value foo or bar to the div as a variable, and pass this variable into the 2 functions? The idea is to make the code cleaner.
import React from 'react';
export default class Sidebar extends React.Component {
isActive(tabName) {
return status = Session.get('currentTab') == tabName ? "active" : "";
}
changeTab(tabName) {
Session.set('currentTab', tabName);
}
render() {
return (
<div id="sidebar-wrapper">
<div
className={this.isActive('foo') + " tab-btn"}
onClick={this.changeTab.bind(null, 'foo')}>
<span className="title">Foo</span>
</div>
<div
className={this.isActive('bar') + " tab-btn"}
onClick={this.changeTab.bind(null, 'bar')}>
<span className="title">Bar</span>
</div>
</div>
)
}
}
You can extract the div into its own reusable component, something like:
export default class InnerDiv extends React.Component {
isActive(tabName) {
return status = Session.get('currentTab') == tabName ? "active" : "";
}
changeTab(tabName) {
Session.set('currentTab', tabName);
}
render() {
var title = this.props.title;
var titleLowerCase = title.toLowerCase();
return (
<div>
className={this.isActive(titleLowerCase) + " tab-btn"}
onClick={this.changeTab.bind(null, titleLowerCase)}>
<span className="title">title</span>
</div>
)
}
}
And then change then render method of Sidebar to a way cleaner one:
render() {
return (
<div id="sidebar-wrapper">
<InnerDiv title="Foo" />
<InnerDiv title="Bar" />
</div>
)
}
I took the liberty to assume the the Session variable is a global one, and there is not issue moving the isActive and changeTab functions into the new component. If this is an issue, please tell me and I will propose another solution.

Categories

Resources