Inputs not changing when removing list items from state - reactjs

I made a simple class that shows a list that you can add or remove li items into state. However, these li items contains input boxes.
Let's say there are 3 li items with 3 input boxes in it. You type something into first list item's input box, then you want to remove that li which contains your filled input.
Even if my index is correct react removes always the last item or I thought it removes the last item, maybe it removes the exact one with the correct index but preserves inputs' values. how can I fix this thing?
class DataTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [
{product: 'a', quantity: 0, price: 0},
],
};
}
render() {
if (!this.props.isOpen) {
return false;
}
const items = this.state.data.map((key, i) => {
return (
<li key={i}>
<input name="text" defaultValue={key.product}/>
<buttun className="btn" onClick={this.removeItem.bind(this, i)}/>
</li>
)
})
return (
<div>
<button className="btn" onClick={this.addItem.bind(this)}>Add Product</button>
<ul>
{items}
</ul>
</div>
)
}
addItem() {
const newState = update(this.state.data, {
$push: [{product: '', quantity: 0, price: 0}]
});
this.setState({
data: newState
})
}
removeItem(index) {
const newArray = update(this.state.data, {
$splice: [[index, 1]]
});
this.setState({
data: newArray
})
}
}
export default DataTable

Don't know if this is your problem but you are using the index of data as the key. This is only fine to do so when you don't modify the collection. Key must stay constant throughout, what's happening is that you remove an item and add another one and React thinks that input is the other one because it's key has changed.
constructor(props) {
super(props);
this.state = {
data: [
{product: 'a', quantity: 0, price: 0},
],
};
this.dataKey = 0;
render() {
if (!this.props.isOpen) {
return false;
}
const items = this.state.data.map((value) => {
return (
<li key={value.key}>
<input name="text" defaultValue={value.product}/>
<buttun className="btn" onClick={this.removeItem.bind(this, i)}/>
</li>
)
})
return (
<div>
<button className="btn" onClick={this.addItem.bind(this)}>Add Product</button>
<ul>
{items}
</ul>
</div>
)
addItem() {
const newState = update(this.state.data, {
$push: [{product: '', quantity: 0, price: 0, key: this.dataKey++}]
});
this.setState({
data: newState
})
}
removeItem(index) {
const newArray = update(this.state.data, {
$splice: [[index, 1]]
});
this.setState({
data: newArray
})
}
}
It just has to be something that is unique and constant. An Id for example would be good for this. https://facebook.github.io/react/docs/lists-and-keys.html

Related

print the updated version of the array in reactjs

I'm trying to delete a data in array using the filter function which return a new array. The problem is how do I push the updated version of the array to the original version?, or if I can't do that, how do I print only the updated version?
here is my state:
export class App extends React.Component {
constructor() {
super();
this.state = {
todos: [
id: 1, nama: 'belajar', status: 'belum selesai',
id: 2, nama: 'kuliah', status: 'belum selesai',
id: 3, nama: 'sekolah', status: 'belum selesai',
id: 4, nama: 'makan', status: 'belum selesai'
]
};
this.state = { value: '' };
this.state = { isReady: false };
this.sayHello = this.sayHello.bind(this);
this.teken = this.teken.bind(this);
this.done = this.done.bind(this);
}
}
here is my code:
done(event) {
this.setState({ isReady: true });
var str = event.target.value;
var arr = str.split();
console.log(this.state.todos);
const list = todos.filter((todos) => todos.nama !== event.target.value);
console.log(list);
this.setState({ todos: list });
this.setState({ nama: event.target.value });
todos.push({
id: event.target.name,
nama: event.target.value,
status: 'selesai'
});
const find = todos.hasOwnProperty(event);
if (find) {
this.setState({ stat: find });
} else {
this.setState({ stat: find });
}
event.preventDefault();
}
and here is how I print my array
<ul className='list-group list-group-flush'>
{todos.map((todos) => {
if (todos.status === 'belum selesai')
return (
<li className='list-group-item'>
{todos.id} {todos.nama}
<button
value={todos.nama}
name={todos.id}
className='btn form-control form-control-sm col-sm-4 bg-light rounded-pill'
onClick={this.done}
>
Done {todos.id}
</button>
</li>
);
else
return (
<li className='list-group-item'>
<s>
{todos.id} {todos.nama}
</s>
</li>
);
})}
</ul>
You are very close. In order to update the list item HTML elements in your component you need to update the list of todos in your state.
done(event) {
// Copy to a new variable.
const nextTodos = this.state.todos.slice();
// Modify however you want.
nextTodos.push({ nama: 'new item' });
// Update the todos. You were missing this part!
this.setState({ todos: nextTodos });
}
In your render function reference this.state.todos like you are doing now.
In the component constructor, set the initial state.
constructor(props) {
super(props);
this.state = {
todos: [
// initial todo data
],
};
}

Change title in this.state

I have this.state with an array, and in array I want to change title, but it is not working. Help me please
Code:
this.state =
{
list: [{title: "1"}, {title: "2"}],
}
// changing title
this.setState({
this.state.list.title: "newTitle"
}],
try this
this.setState({
list: [{title: 'new title'}, { title: 'other title' }]
});
Example
class App extends React.Component {
state = { list: [{title: "1"}, {title: "2"}] };
updateTitle = (index) => {
var { list } = this.state;
list[index].title = `Updated title ${index}`;
this.setState({list});
}
updateTitles = () => {
var { list } = this.state;
list.forEach((item, k) => {
item.title = `Title ${k}`;
});
this.setState({list});
}
render(){
var { list } = this.state;
return (
<div>
<button onClick={this.updateTitles}>Update Title</button>
<ul>
{list.map((item, k) => (
<li onClick={() => this.updateTitle(k)} key={item.title}>{item.title}</li>
))}
</ul>
</div>
);
}
}
I can't get your question correctly. If you want to change value of title key of every child of list array just declare a function like this
function changeValue () {
let newList = this.state.list
newList.forEach(item => {
item.title = 'newTitle'
})
this.setState = ({
list: newList
})
}

how to push a new element into an array from the state

I'm trying to push in elements into an array called 'this.state.tags'. On the console, I see the elements pushing into the array. However, when I add something, the array comes out blank, when I add more items I only the see the previous items I've added. I'm not seeing the newest item I've pushed in.
I've done Object.assign([], this.state.tags) from the child component Grades.js. Then I pushed in 'this.state.newTag' and I've reset the state to that new result.
//From Grades.js, the child component
state = {
toggle: null,
newTag: '',
tags: []
}
addTags = (event) => {
event.preventDefault();
let newTagArr = Object.assign([], this.state.tags)
newTagArr.push(this.state.newTag)
this.setState({
tags: newTagArr
})
// this will pass on to the parent
this.props.filterTags(this.state.tags)
}
render() {
const { tags } = this.state
let tagList = tags.map((item, index) => {
return (
<li key={index} className="tagListItem">{item}</li>
)
})
return(
<div>
<ul className="tagList">{tagList}</ul>
<form onSubmit={this.addTags}>
<input
placeholder="Add a tag"
name="newTag"
onChange={this.handleInput}
style={{border: '0', borderBottom: '1px solid #000'}}
/>
</form>
</div>
)
}
// From App.js, the parent component
state = {
students: [],
filteredStudents: [],
filteredByTag: [],
search: '',
tag: '',
toggle: false
}
componentDidMount() {
axios.get('https://www.hatchways.io/api/assessment/students')
.then(result => {
let newArr = Object.assign([], result.data.students);
let newResult = newArr.map(elem => {
return {city: elem.city, company: elem.company, email: elem.email,
firstName: elem.firstName.toLowerCase(), lastName: elem.lastName.toLowerCase(),
grades: elem.grades, id: elem.id, pic: elem.pic, skill: elem.skill}
})
this.setState({
students: newResult
})
})
.catch(err => console.log(err))
}
tagFiltering = (param) => {
console.log(param)
this.state.students.push()
}
I expect the output to be ["tag1", "tag2", "tag3"]
Not ["tag1", "tag2"], when I've already typed in tag1, tag2 and tag3
Use ES2015 syntax :
this.setState({
tags: [...this.state.tags , this.state.newTag]
})
In react the state is immutable meaning that we have to provide new state object every time, we call the setState method.

ReactJS won't print an array in the order that it is stored?

I have an issue with my reactjs code where the render() function won't print an array in the order that it is stored in my state object.
Here's my code which is pretty simple:
import React from "react";
export default class DonationDetail extends React.Component {
constructor(props) {
super(props);
this.state = { content: [] };
}
componentDidMount() {
let state = this.state;
state.content.push({ food: "burger" });
state.content.push({ food: "pizza" });
state.content.push({ food: "tacos" });
this.setState(state);
}
addPaymentItem() {
const item = { food: "" };
let state = this.state;
state.content.unshift(item);
this.setState(state);
}
render() {
console.log(this.state);
let ui = (
<div>
<button type="button" onClick={() => this.addPaymentItem()}>
add to top
</button>
{this.state.content.map((item, key) => (
<input type="text" key={key} defaultValue={item.food} />
))}
</div>
);
return ui;
}
}
When you press the button add to top, a new item is placed to the front of the state.content array, which you can verify from the console.log(this.state) statement. But what's unusual is that the HTML that is generated does NOT add this new item to the top of the user interface output. Instead, another input field with the word taco is placed at the bottom of the list in the user interface.
Why won't ReactJS print my state.content array in the order that it is actually stored?
You can use the array index as key when the order of the elements in the array will not change, but when you add an element to the beginning of the array the order is changed.
You could add a unique id to all your foods and use that as key instead.
Example
class DonationDetail extends React.Component {
state = { content: [] };
componentDidMount() {
const content = [];
content.push({ id: 1, food: "burger" });
content.push({ id: 2, food: "pizza" });
content.push({ id: 3, food: "tacos" });
this.setState({ content });
}
addPaymentItem = () => {
const item = { id: Math.random(), food: "" };
this.setState(prevState => ({ content: [item, ...prevState.content] }));
};
handleChange = (event, index) => {
const { value } = event.target;
this.setState(prevState => {
const content = [...prevState.content];
content[index] = { ...content[index], food: value };
return { content };
});
};
render() {
return (
<div>
<button type="button" onClick={this.addPaymentItem}>
add to top
</button>
{this.state.content.map((item, index) => (
<input
type="text"
key={item.id}
value={item.food}
onChange={event => this.handleChange(event, index)}
/>
))}
</div>
);
}
}
ReactDOM.render(<DonationDetail />, 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>
Instead of:
componentDidMount() {
let state = this.state;
state.content.push({ food: "burger" });
state.content.push({ food: "pizza" });
state.content.push({ food: "tacos" });
this.setState(state);
}
Try
componentDidMount() {
this.setState(prevState => ({
content: [
...prevState.content,
{ food: "burger" },
{ food: "pizza" },
{ food: "tacos" },
]
}));
}
and
addPaymentItem() {
const item = { food: "" };
let state = this.state;
state.content.unshift(item);
this.setState(state);
}
to
addPaymentItem() {
this.setState(prevState => ({
content: [
{ food: "" },
...prevState.content,
]
}));
}

How to update state in map function in reactjs

I am having 4 buttons each button have name id and selected boolean flag.
What I am trying to achieve is, on click of button, boolean button flag should be changed of that particular button. For this, I need to setState in map function for that particular button Id.
My issue is I am unable to setState in map function for that particular clicked button, its btnSelected should be changed
My aim is to create a multi-select deselect button.Its kind of interest selection for the user and based on that reflect the UI as well my array. Here is my code.
Thanks in anticipation.
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
export default class Test extends Component {
constructor(props, context) {
super(props, context);
this.handleChange = this.handleChange.bind(this);
this.state = {
value: "",
numbers: [1, 2, 3, 4, 5],
posts: [
{
id: 1,
topic: "Animal",
btnSelected: false
},
{
id: 2,
topic: "Food",
btnSelected: false
},
{
id: 3,
topic: "Planet",
btnSelected: false
},
{ id: 4, topic: "Nature", btnSelected: false }
],
allInterest: []
};
}
handleChange(e) {
//console.log(e.target.value);
const name = e.target.name;
const value = e.target.value;
this.setState({ [name]: value });
}
getInterest(id) {
this.state.posts.map(post => {
if (id === post.id) {
//How to setState of post only btnSelected should change
}
});
console.log(this.state.allInterest);
if (this.state.allInterest.length > 0) {
console.log("Yes we exits");
} else {
console.log(id);
this.setState(
{
allInterest: this.state.allInterest.concat(id)
},
function() {
console.log(this.state);
}
);
}
}
render() {
return (
<div>
{this.state.posts.map((posts, index) => (
<li
key={"tab" + index}
class="btn btn-default"
onClick={() => this.getInterest(posts.id)}
>
{posts.topic}
<Glyphicon
glyph={posts.btnSelected === true ? "ok-sign" : "remove-circle"}
/>
</li>
))}
</div>
);
}
}
Here's how you do something like this:
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (e) => {
const { posts } = this.state;
const { id } = e.target;
posts[id].selected = !this.state.posts[id].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="radio" id={i} key={i} checked={p.selected} onClick={this.handleClick} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here.
You can do this by passing the index from the map into each button's handleClick function, which would then return another function that can be triggered by an onClick event.
In contrast to Colin Ricardo's answer, this approach avoids adding an id prop onto each child of the map function that is only used for determining the index in the handleClick. I've modified Colin's example here to show the comparison. Notice the event parameter is no longer necessary.
class App extends Component {
state = {
posts: [{
name: 'cat',
selected: false,
}, {
name: 'dog',
selected: false
}]
}
handleClick = (index) => () => {
const { posts } = this.state;
posts[index].selected = !this.state.posts[index].selected
this.setState({ posts })
}
render() {
return (
<div>
<form>
{this.state.posts.map((p, i) => {
return (
<div>
<label>{p.name}</label>
<input type="checkbox" key={i} checked={p.selected} onClick={this.handleClick(i)} />
</div>
)
})}
</form>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Working example here

Resources