Why console says my keys are not unique? - javascript

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.

Related

How to delete a DOM element from an array after you've clicked on it?

I was making a simple to-do list. You submit itens from an input and they go to the To-DO section. When you click over them they go to the 'Done' section. And when you click on them again, they vanish forever. It was all working fine.
But I realized the doneItens array kept growing in length, which I wanted to optimize. So I came up with this line of code
doneItens.splice(i, 1);
which goes inside an onclick event, which you can see in the code inside the deleteDone function.
That gives the error, though,
Error:{
"message": "Uncaught TypeError: doneItens.splice is not a function"
If I put it outside and below the onclick event it also doesn't work. How can I do it?
var input = document.getElementById('play');
var toDo = document.getElementsByTagName('ol')[0];
var done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
const newItem = document.createElement('li');
newItem.setAttribute('class', 'item');
newItem.append(input.value);
toDo.append(newItem);
input.value='';
deleteItem();
}
function deleteItem() {
const toBeDone = document.getElementsByClassName('item');
for(let i = 0; i < toBeDone.length; i++) {
toBeDone[i].onclick = () => {
appendItemDone(toBeDone[i]);
toBeDone[i].style.display = 'none';
deleteDone();
}
}
}
function appendItemDone(item) {
const newDone = document.createElement('li');
newDone.setAttribute('class', 'feito')
newDone.append(item.innerText);
done.append(newDone);
}
function deleteDone() {
const doneItens = document.getElementsByClassName('feito');
console.log('done length', doneItens.length)
for (let i = 0; i < doneItens.length; i++) {
doneItens[i].onclick = () => {
doneItens[i].style.display = 'none';
doneItens.splice(i, 1);
}
}
}
<div id='flex'>
<form class='form' onsubmit='handleSubmit(event)'>
<input placeholder='New item' type='text' id='play'>
<button>Send</button>
</form>
<div id='left'>
<h1 id='todo' >To-do:</h1>
<p class='instruction'><i>(Click over to mark as done)</i></p>
<ol id='here'></ol>
</div>
<div id='right'>
<h1>Done:</h1>
<p class='instruction'><i>(Click over to delete it)</i></p>
<p id='placeholder'></p>
<ol id='done'></ol>
</div>
</div>
With the use of JavaScript DOM API such as Node.removeChild(), Element.remove() and Node.parentNode, your task can be solved with this code:
const input = document.getElementById('play');
const todo = document.getElementById('todo');
const done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
// create new "todo" item
const newTodo = document.createElement('li');
newTodo.textContent = input.value;
todo.append(newTodo);
// clean the input field
input.value = '';
// listen to "click" event on the created item to move it to "done" section
newTodo.addEventListener('click', moveToDone);
}
function moveToDone(event) {
// remove "click"-listener to prevent event listener leaks
event.target.removeEventListener('click', moveToDone);
// move clicked todo-element to "done" section
const newDone = event.target.parentNode.removeChild(event.target);
done.append(newDone);
// listen to "click" event on the moved item to then completely delete it
newDone.addEventListener('click', removeFromDone);
debugElementsLeak();
}
function removeFromDone(event) {
// remove "click"-listener to prevent event listener leaks
event.target.removeEventListener('click', removeFromDone);
// complete remove clicked element from the DOM
event.target.remove();
debugElementsLeak();
}
function debugElementsLeak() {
const todoCount = todo.childElementCount;
const doneCount = done.childElementCount;
console.log({ todoCount, doneCount });
}
<div id="flex">
<form class="form" onsubmit="handleSubmit(event)">
<input placeholder="New item" type="text" id="play">
<button>Add item</button>
</form>
<div id="left">
<h1>To-do:</h1>
<p class="instruction"><em>(Click over to mark as done)</em></p>
<ol id="todo"></ol>
</div>
<div id="right">
<h1>Done:</h1>
<p class="instruction"><em>(Click over to delete it)</em></p>
<p id="placeholder"></p>
<ol id="done"></ol>
</div>
</div>
You'll want to use splice,
and then rather than use hidden, 'refresh' the done element by adding all elements in the spliced array.
I've commented my code where I've made changes and why
var input = document.getElementById('play');
var toDo = document.getElementsByTagName('ol')[0];
var done = document.getElementById('done');
function handleSubmit(event) {
event.preventDefault();
const newItem = document.createElement('li');
newItem.setAttribute('class', 'item');
newItem.append(input.value);
toDo.append(newItem);
input.value='';
deleteItem();
}
function deleteItem() {
const toBeDone = document.getElementsByClassName('item');
for(let i = 0; i < toBeDone.length; i++) {
toBeDone[i].onclick = () => {
appendItemDone(toBeDone[i].cloneNode(true));
toBeDone[i].style.display = 'none';
deleteDone();
}
}
}
function appendItemDone(item) {
const newDone = document.createElement('li');
newDone.setAttribute('class', 'feito')
newDone.append(item.innerText);
done.append(newDone);
}
function deleteDone() {
var doneItens = document.getElementsByClassName('feito');
for (let i = 0; i < doneItens.length; i++) {
doneItens[i].onclick = () => {
var splicedArray = spliceFromArray(doneItens,doneItens[i]);// NEW BIT -CALL NEW SPLICE FUNCTION
done.innerHTML=""; // NEW BIT - SET OVERALL DONE TO BLANK ON DELETE
for(var index in splicedArray){// NEW BIT - fOR EACH RETURNED ELEMENT IN THE SPLICE, ADD IT TO THE OVERALL DONE ELEMENT
done.appendChild(splicedArray[index]);
}
}
}
}
function spliceFromArray(arrayInput,element){// NEW BIT - SPLICE FUNCTION THAT RETURNS SPLICED ARRAY
var array = Array.from(arrayInput);
var index = array.indexOf(element);
if(index!=-1){
if(array.length==1 && index == 0){
array = [];
}
else{
array.splice(index,1);
}
}
return array;
}
<div id='flex'>
<form class='form' onsubmit='handleSubmit(event)'>
<input placeholder='New item' type='text' id='play'>
<button>Send</button>
</form>
<div id='left'>
<h1 id='todo' >To-do:</h1>
<p class='instruction'><i>(Click over to mark as done)</i></p>
<ol id='here'></ol>
</div>
<div id='right'>
<h1>Done:</h1>
<p class='instruction'><i>(Click over to delete it)</i></p>
<p id='placeholder'></p>
<ol id='done'></ol>
</div>
</div>

React OnClick for item not working

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>

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);

Reusing properties within map loop in ReactJS - best practice

I am trying to figure out the best way to render two blocks of code in ReactJS, one will be used for desktop and the other for mobile. Functionality wise they will do exactly the same thing but have different markup wrapped around them, an example would be a carousel that renders differently on mobile.
I am using the map function to iterate over the object properties, I have working code below but I am duplicating variables and reassigning the same values which is obviously inefficient as I am doing this for each code block.
Can anyone suggest a nice / best practice way of doing what I need?
Sorry for the basic question!
render: function() {
return (
<div>
<div className="hidden-xs">
{
this.state.userItems.map(function (item) {
var someValue = 'value' in item ? item.value : '';
var anotherValue = 'anotherValue' in item ? item.anotherValue : '';
return (
<div key={someValue}>
{someValue}<br>{anotherValue}
</div>
)
})
}
</div>
<div className="visible-xs">
{
this.state.userItems.map(function (item) {
var someValue = 'value' in item ? item.value : '';
var anotherValue = 'anotherValue' in item ? item.anotherValue : '';
return (
<div key={someValue}>
<div className="differentMarkup">
{someValue}<br>{anotherValue}
</div>
</div>
)
})
}
</div>
</div>
)}
Updated answer:
If the inner items can have different markup, I would say it depends how different their markup is going to be. For example, you could have two separate methods that prepare the markup of the mobile and non-mobile version of the items separately. Something like this:
renderUserItem: function(itemData) {
return (
<div key={itemData.someValue}>
{itemData.someValue}<br>{itemData.anotherValue}
</div>
)
},
renderMobileUserItem: function(itemData) {
return (
<div key={itemData.someValue}>
<div className="differentMarkup">
{itemData.someValue}<br>{itemData.anotherValue}
</div>
</div>
)
},
prepareItemData: function(item) {
return {
someValue: item.value ? item.value : '';
anotherValue: item.anotherValue ? item.anotherValue : '';
};
},
render: function() {
// Prepare the variables you will need only once
let parsedUserItems = this.state.userItems.map(this.prepareItemData);
return (
<div>
<div className="hidden-xs">
{ parsedUserItems.map(this.renderUserItem) }
</div>
<div className="visible-xs">
{ parsedUserItems.map(this.renderMobileUserItem) }
</div>
</div>
)
}
You could also have a single method if the differences between mobile and non-mobile are not too big. For example, using ES6 arrow functions:
renderUserItem: function(itemData, renderWrapper) {
return (
<div key={itemData.someValue}>
{renderWrapper ? <div className="differentMarkup"> : ''}
{itemData.someValue}<br>{itemData.anotherValue}
{renderWrapper ? </div> : ''}
</div>
)
},
render: function() {
// Prepare the variables you will need only once
let parsedUserItems = this.state.userItems.map(this.prepareItemData);
return (
<div>
<div className="hidden-xs">
{ parsedUserItems.map(item => this.renderUserItem(item, false)) }
</div>
<div className="visible-xs">
{ parsedUserItems.map(item => this.renderUserItem(item, true)) }
</div>
</div>
)
}
Original answer below:
If I understood correctly and the inner items have the same markup, you could extract the function passed into map to a separate method:
renderUserItem: function(item) {
var someValue = 'value' in item ? item.value : '';
var anotherValue = 'anotherValue' in item ? item.anotherValue : '';
return (
<div key={someValue}>
{someValue}<br>{anotherValue}
</div>
)
},
render: function() {
return (
<div>
<div className="hidden-xs">
{ this.state.userItems.map(this.renderUserItem) }
</div>
<div className="visible-xs">
{ this.state.userItems.map(this.renderUserItem) }
</div>
</div>
)
}

Categories

Resources