this.state.items.map is not a function - javascript

I do console.log(items) I got ['a','b','c'] but I got error of map is not a function.
..
var Todo_list = React.createClass({
getInitialState(){
return { items:['a','b']}
},
addItem(item){
this.setState({items:this.state.items.push(item)})
console.log(this.state.items) // this is working
},
render() {
return (
<div>
<TodoInput addItem={this.addItem} />
{this.state.items.map((item,i) => <li key={i}>{item}</li> )}
</div>
);
}
});
..
https://jsfiddle.net/tehwajh2/ Try to add an item, I wonder why, I guess I've pushed it correctly?

In this case you can use .concat instead of .push, because .push returns the new length of the array., length is Number, and Number does not have .map method that's why you get error
this.setState({ items: this.state.items.concat(item) })
Example
The push() method adds one or more elements to the end of an array and
returns the new length of the array.
The concat() method returns a new array comprised of the array on
which it is called joined with the array(s) and/or value(s) provided
as arguments.

You can try adding this.state.items.push(item); seperately. It works
var TodoInput = React.createClass({
handleAddItem(){
var todo_val = this.refs.todo_val.value;
this.props.addItem(todo_val);
},
render() {
return (
<div>
<input ref='todo_val' type="text" />
<button onClick={this.handleAddItem}>Add</button>
</div>
);
}
});
var Todo_list = React.createClass({
getInitialState(){
return { items:['a','b']}
},
addItem(item){
console.log(item);
this.state.items.push(item);
this.setState({items: this.state.items});
console.log(this.state.items)
},
render() {
return (
<div>
<TodoInput addItem={this.addItem} />
{this.state.items.map(function(item, key) {
return (
<li key={key}> {item}</li>
)
})}
</div>
);
}
});
ReactDOM.render(
<div>
<Todo_list />
</div>,
document.getElementById('container')
);
JSFIDDLE

Related

Strange behaviour when removing item from state array

I have an array in state which contains various components. When I click the remove button on one of the components, it removes the first item from the array instead. I only seem to have this problem when using components in the array, it works fine with an array of strings.
Parent component:
addItem = (block) => {
const add = [...this.state.items, block];
this.setState({items: add})
}
removeItem = (index) => {
const remove = [...this.state.items];
remove.splice(index, 1);
this.setState({items: remove})
}
render(){
return(
<div className="Board">
<div className="BoardContainer">
{this.state.items.map((item, index) => { return <Item remove= {this.removeItem} key={index} >{item}</Item>})}
</div>
<button onClick={() => this.addItem(<BannerImage />)}>Banner Image</button>
<button onClick={() => this.addItem(<ShortTextCentered />)}>Short Text Centered</button>
</div>
)
}
Child component:
export class Item extends React.Component {
handleRemove = () => {
this.props.remove(this.props.index)
}
render() {
return (
<div className="item">
{this.props.children}
<button onClick={this.handleRemove} >Remove</button>
</div>
)
}
}
You used inside your component 'this.props.index' but you didn't pass the index to your component.
you should do something like this:
{this.state.items.map((item, index) => { return <Item remove={this.removeItem} key={index} index={index} >{item}</Item>})}
Array.prototype.splice() mutates the array, so it's better not to use splice() with React.
Easiest to use Array.prototype.filter() to create a new array.
Furthermore working with unique id's rather than indexes prevents from unexpected results.
let filteredArray = this.state.item.filter(x=> x!== e.target.value)
this.setState({item: filteredArray});

Render a map of div and their title

I would like to simplify "renderTitle" and "renderComments" in a unique function in a React component:
renderTitle(dish) {
return (
<h2>
Title array comment
</h2>
);
}
renderComments(dish) {
return (
dish.array.map(comment => {
return (
<div>
hello
</div>
);
})
);
}
render() {
return (
{this.renderTitle(this.props.dish)}
{this.renderComments(this.props.dish)}
);
}
Take a look at below code where I used Fragment (react 16.x). And see how I merged the functions in your question.
renderItems(dish) {
const comments = dish.array.map(comment => (<div>hello</div>))
return (
<React.Fragment>
<h2>
Title array comment
</h2>
{comments}
</React.Fragment>
);}
To use regular JavaScript expressions in the middle of JSX you have to wrap the JavaScript expression in {}.
Note that to return multiple elements on the same level you should either return an array or wrap them in a Fragment:
render() {
return (
<React.Fragment>
<h2>
Title array comment
</h2>
{
dish.array.map(comment => (
<div>
hello
</div>
));
}
</React.Fragment>
);
}
You can do this if this.props.dish is array.
renderTitle(dish) {
return dish.map(i => {
return <div>{i.title}</div>;/*this title may change*/
});
}
renderComments(dish) {
return dish.map(i => {
return <div>{i.comment}</div>;/*this comment may change*/
});
}
render() {
return (
<div>
{this.renderTitle(this.props.dish)}
{this.renderComments(this.props.dish)}
</div>
);
}

React: clicking on an item in a list and print out its current state

I am mapping over a list of objects in state and creating a list.
However, I'd like to add some functionality that will allow me to change the state of one of the items in the list.
In the code below, I have a helper function called handleEdit(e). In this function, I'd like to print out its state. IE - {name:'Eric', update: false}
What should I put in there to achieve this?
function AddPerson(props) {
return(
<div>
<input type="text" value= {props.newPerson} onChange = {props.handleUpdate}/>
<button type="submit" onClick= {props.addNewFriend}> Add New </button>
</div>
)
}
function Person(props) {
console.log(props.handleEdit)
return (
props.listOfPeople.map((person, i) => {
return(
<li key={i} onClick = {props.handleEdit}>{person['name']}</li>
)
})
)
}
function ListPeople(props) {
return(
<div>
<ul>
<Person listOfPeople = {props.people} handleEdit = {props.edit}/>
</ul>
</div>
)
}
class App extends Component {
constructor(props) {
super(props)
this.state = {
newPerson: '',
people: [{name:'Eric', update: false} , {name:'Rick', update:false}, {name:'Yoni', update:false}]
};
this.handleUpdate = this.handleUpdate.bind(this)
this.addNewFriend = this.addNewFriend.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleUpdate(e) {
this.setState({newPerson: e.target.value})
}
addNewFriend(){
console.log(this.state.newPerson)
const newFriendList = this.state.people.slice()
this.setState(
{
newPerson: '',
people: newFriendList.concat({name:this.state.newPerson, update:false})
}
)
}
handleEdit(e) {
console.log(e.target.value)
return null
}
render() {
return (
<div>
<AddPerson handleUpdate = {this.handleUpdate} addNewFriend = {this.addNewFriend} newPerson = {this.state.newPerson} />
<ListPeople people = {this.state.people} edit={this.handleEdit} />
</div>
);
}
}
In your Person component list pass the onClick handler like this
props.listOfPeople.map((person, i) => {
return(
<li key={i} onClick = {(e) => {props.handleEdit(e,person)}}>{person['name']}</li>
)
})
And then in handleEdit the second argument is the person value you want
handleEdit(e, person) {
console.log(person)
return null
}
Replace this line <Person listOfPeople = {props.people} handleEdit = {props.edit}/> with <Person listOfPeople = {props.people} handleEdit = {props.edit.bind(this, this.state)}/>
and change method as
handleEdit(state) {
console.log(state);
return null
}
To get the data on your grandparent component you can pass it through the function itself.
Example
// change your function to get person object with the event object together
handleEdit(event, person) {
console.log(event.target.value);
console.log(person);
return null
}
// In your Person component pass the event and person objects to the function
function Person(props) {
console.log(props.handleEdit)
return (
props.listOfPeople.map((person, i) => {
return(
<li key={i} onClick={(event) => props.handleEdit(event, person)}>
{person['name']}
</li>
)
})
)
}

pass index from function to function in react

I have a list of item, and upon on click of the delete button the item will get removed. I know the steps to do it but I'm stuck on how can I pass the key to the dlt_item scope.
http://jsfiddle.net/3Ley7uac/1/
var App = React.createClass({
getInitialState(){
return {
items:[1,2,3]
}
},
dlt_item(key){
//how to get index/id here?
},
renderItem(){
return this.state.items.map((item,i)=> <li key={i}>{item}
<button>Edit</button>
<button onClick={this.dlt_item}>Delete</button>
</li>
)
},
render(){
return(
<ul>
{this.renderItem()}
</ul>
)
}
})
You need to bind this.dlt_item as
<button onClick={this.dlt_item.bind(this, i)}>Delete</button>
and in your dlt_item function you can splice your state array from this index passed.
Code
var App = React.createClass({
getInitialState(){
return {
items:[1,2,3]
}
},
dlt_item(key){
console.log(key);
this.state.items.splice(key, 1);
this.setState({items: this.state.items});
//how to get index/id here and do setState
},
renderItem(){
return this.state.items.map((item,i)=> <li key={i}>{item}
<button>Edit</button>
<button onClick={this.dlt_item.bind(this, i)}>Delete</button>
</li>
)
},
render(){
return(
<ul>
{this.renderItem()}
</ul>
)
}
})
React.render(<App />, document.getElementById('container'));
JSFIDDLE
Instead of splice you can use filter as
dlt_item(key){
var items = this.state.items.filter(function(obj){
return obj != (key + 1);
});
console.log(items);
this.setState({items: items});
//how to get index/id here and do setState
},
JSFIDDLE
Use .bind(this, yourKey)
In your example:
var App = React.createClass({
getInitialState(){
return {
items: [1, 2, 3]
}
},
dlt_item(key){
console.log(key);
},
renderItem(){
return this.state.items.map((item, i) => <li key={i}>{item}
<button>Edit</button>
<button onClick={this.dlt_item.bind(this, item)}>Delete</button>
</li>
)
},
render(){
return (
<ul>
{this.renderItem()}
</ul>
)
}
});
React.render(<App />, document.getElementById('container'));
Another way to achieve the same result could be to return a curried function from you Component
The method will take a value and wait for the event to be called before executing the action.
I prefer this way as I like to limit the javascript in JSX as much as possible.
dlt_item(key){
// return the event listener
return function(e) {
// do something with the state
}.bind(this) // bind the inner functions this to the Component
}
The when you want to call the function you can do it like this
<button onClick={this.dlt_item(i)}>Delete</button>
var App = React.createClass({
getInitialState(){
return {
items:[1,2,3]
}
},
// this function will take your key
dlt_item(key){
// return the event listener
return function(e) {
this.setState({
items: splice(this.state.items, key, 1)
})
}.bind(this)
},
renderItem(){
return this.state.items.map((item,i)=> (
<li key={i}>{item}
<button>Edit</button>
<button onClick={this.dlt_item(i)}>Delete</button>
</li>
))
},
render(){
return(
<ul>
{this.renderItem()}
</ul>
)
}
})
// immutable splice helper function
const splice = (arr, index, count = 0, ...items) => {
return [
...arr.slice(0, index),
...items,
...arr.slice(index + count)
]
}
ReactDOM.render(
<App />,
document.getElementById('app')
)
<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>
<main id="app"></main>

i is not defined using map() with ES6

Trying to create a li in react but failed. Error is near the map(), I got error of i is not defined, why?
const TodoItems = React.creatClass({
getInitialState() {
return {
items : [
{id:1,name:"Gym"},
{id:2,name:"Jump"},
{id:3,name:"Racing"}
]
}
},
renderItem(){
return(
<ul>
this.state.items.map(item,i =>
<li key={i}>item.name</li>
)
</ul>
)
},
render(){
return (
<renderItem />
)
}
})
When you have multiple arguments for an arrow function, you need to put () around them. So:
this.state.items.map((item,i) =>
// ------------------^------^
<li key={i}>item.name</li>
)
Your original code calls map with item as its first argument, and an arrow function taking a single argument (i) as its second argument.
You also need to put item.name in {} and put the call to map in {}:
renderItem(){
return(
<ul>
{this.state.items.map((item,i) =>
<li key={i}>{item.name}</li>
)}
</ul>
)
Then it works:
const { Component } = React;
const { render } = ReactDOM;
const TodoItems = React.createClass({
getInitialState() {
return {
items : [
{id:1,name:"Gym"},
{id:2,name:"Jump"},
{id:3,name:"Racing"}
]
}
},
renderItem(){
return(
<ul>
{this.state.items.map((item,i) =>
<li key={i}>{item.name}</li>
)}
</ul>
)
},
render(){
return this.renderItem();
}
});
render(<TodoItems /> , document.getElementById('items'));
<div id="items"></div>
<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>
That became clear to me when I used Babel's REPL to compile the JSX and realized I was seeing "this.state.map((item,i) =>" as a string.
try this :
renderItem(){
return(
<ul>
{this.state.items.map((item,i) => {
return(
<li key={i}>item.name</li>);
})}
</ul>
)

Categories