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>
Related
I just started to learn to react 2 days ago and I'm having a hard time with react's setstate method, all I know is use revstate parameter if want to change state based on previous state, and callback parameter to be executed right after the state change (please correct me if this wrong), so I just change the array content (which I render it using javascript's array.map) and I wish it renders right after the state is changed, it is changing but delayed, it only render after I do another click but the render method is called
for any senpai out there thanks for the help.
Handle click for changing to render content based on index passed on my button "onClick"
class App extends React.Component {
constructor(props){
super(props)
this.state = {
clickeditem : -1
}
this.torender = [
{
display : "first",
content : []
},
{
display : "second",
content : []
}
]
}
handleclick = (i) =>{
this.setState(prevstate=>{
if (prevstate.clickeditem === -1) {
return {clickeditem : i}
} else {
return prevstate.clickeditem === i ? {clickeditem : -1} : {clickeditem : i}
}
},() => {
return this.state.clickeditem === -1 ? (this.torender[0].content = [], this.torender[1].content = [])
: (this.state.clickeditem === 0) ? (this.torender[0].content = ["torender-0 content","torender-0 content"],this.torender[1].content = [])
: (this.state.clickeditem === 1) ? (this.torender[1].content = ["torender-1 content","torender-1 content"],this.torender[0].content = [])
: null
})
}
render(){
return(
<div>
<ul>
{
this.torender.map((item,index) => {
return(
<li key = {index}>
{item.display}
<ul>
{item.content.map((content,contentindex) => {
return(<li key = {contentindex}>{content}</li>)
})}
</ul>
</li>
)
})
}
</ul>
<button onClick={()=>this.handleclick(0)}>first-button</button>
<button onClick={()=>this.handleclick(1)}>second-button</button>
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('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>
Refactor your code and Approach the simpler way
Actually, you shouldn't use the second param callback.
Whenever the state is changed, the life cycle of React Js will re-render it properly (See the image below to clarify in detail ^^!)
There are some things to note:
Move the content of each item in torender accordingly --> This is clearer about the initial data as well as it should not be mutated.
Default clickeditem is one of the items in torender, for example, the first item.
After that, you just control the content to be rendered in this way
___________ The condition to call renderContent() method ______________
{index === this.state.clickeditem && this.renderContent(item)}
_____________renderContent() looks like below_____________
renderContent = (item) => {
return (
<ul>
{item.content.map((content, contentindex) => {
return <li key={contentindex}>{content}</li>;
})}
</ul>
);
};
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
clickeditem: 0
};
this.torender = [
{
display: "first",
content: ["torender-0 content", "torender-0 content"]
},
{
display: "second",
content: ["torender-1 content", "torender-1 content"]
}
];
}
handleclick = (index) => {
this.setState({clickeditem: index});
};
renderContent = (item) => {
return (
<ul>
{item.content.map((content, contentindex) => {
return <li key={contentindex}>{content}</li>;
})}
</ul>
);
};
render() {
return (
<div>
<ul>
{this.torender.map((item, index) => {
return (
<li key={index}>
{item.display}
{index === this.state.clickeditem && this.renderContent(item)}
</li>
);
})}
</ul>
<button onClick={() => this.handleclick(0)}>first-button</button>
<button onClick={() => this.handleclick(1)}>second-button</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('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>
Issue
this.torender isn't part of react state, the component would need to rerender once more to see the mutation you did in the previous render cycle.
Solution
It is best to just compute it's value when you are rendering your UI then it should work as I suspect you meant it to.
handleclick = (i) =>{
this.setState(prevstate=>{
if (prevstate.clickeditem === -1) {
return { clickeditem: i }
} else {
return prevstate.clickeditem === i ? { clickeditem: -1 } : { clickeditem: i }
}
})
}
render(){
const { clickeditem } = this.state;
let torender = [
{
display : "first",
content : []
},
{
display : "second",
content : []
}
];
if (clickeditem === -1) {
torender[0].content = [];
torender[1].content = [];
} else if (clickeditem === 0) {
torender[0].content = ["torender-0 content","torender-0 content"];
torender[1].content = [];
} else if (clickeditem === 1) {
torender[1].content = ["torender-1 content","torender-1 content"];
torender[0].content = [];
} else {
torender = []; // <-- render nothing
}
return(
<div>
<ul>
{torender.map((item,index) => {
return(
<li key = {index}>
{item.display}
<ul>
{item.content.map((content, contentindex) => (
<li key={contentindex}>{content}</li>;
))}
</ul>
</li>
)
})
}
</ul>
<button onClick={()=>this.handleclick(0)}>first-button</button>
<button onClick={()=>this.handleclick(1)}>second-button</button>
</div>
)
}
}
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>
)
})
)
}
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
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>
)
Anything wrong with my map method?
var App = React.createClass({
getInitialState(){
return {
items:[1,2,3]
}
},
renderItem(){
return(
this.state.items.map((item,i))=>
<li key={i}> {item} </li>
)
},
render(){
return(
<ul>
{this.renderItem()}
</ul>
)
}
})
Couldn't see anything rendered, checked the console, no error found.
You have a syntax problem:
this.state.items.map((item,i))=>
^
Remove that close parenthesis, and place it beside the next close parenthesis:
return(
this.state.items.map((item,i)=>
<li key={i}> {item} </li>
))
First of all you need to get rif of the extra ) in the map parameters list
this.state.items.map((item,i))=> to this.state.items.map((item,i)=>
Secondly you need to add another ) to close the return statement
var App = React.createClass({
getInitialState(){
return {
items:[1,2,3]
}
},
renderItem(){
return(
this.state.items.map((item,i)=>
<li key={i}> {item} </li>
)
)
},
render(){
return(
<ul>
{this.renderItem()}
</ul>
)
}
})
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="app"></div>
Try this, IMO it's cleaner this way
var App = React.createClass({
getInitialState(){
return {
items:[1,2,3]
}
},
renderItem(item, index){
return(
<li key={index}> {item} </li>
)
},
render(){
return(
<ul>
{this.state.items.map(this.renderItem, this)}
</ul>
)
}
})
renderItem(){
return(
this.state.items.map((item,i)=> {
return <li key={i}> {item} </li>;
}
)
},
problem is in .map, you have to place code in {} and return