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.
Related
I am maintaining an array of objects which is stored in a state object. Basically I am pushing each object to this array whenever I click on Add button .This stores this object in array.
Also I am iterating this array of objects to display down the page.
Right now I am trying to fill the input fields based on the object that I have clicked. I am unable to do it. Basically, the object that I have clicked should populate the input fields and then I should be able to edit it
Help would be appreciated
The structure of array of objects:
users= [
{"name":"xxx","email":"yyy","phone":"656"},
{"name":"yyy","email":"xxx","phone":"55"}
];
Component Code
import * as React from 'react';
interface IState{
users : Account[];
user: Account
}
interface Account{
name: string;
email: string;
phone: string
}
export default class App extends React.Component<{},IState> {
constructor(props:any){
super(props);
this.state= {
users: [],
user: {
name: '',
email: '',
phone: '',
}
}
}
removeAccount = (i:number) => {
let users = [...this.state.users];
users.splice(i,1);
this.setState({users},()=>{console.log('setting the data')});
}
handleChange = ( event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({
user:{
...this.state.user,
[event.currentTarget.name]:event.currentTarget.value
}
})
}
onAdd = () => {
e.preventDefault();
this.setState({
users: [...this.state.users, this.state.user],
user: { name:'', email: '', phone: ''}
},()=>{console.log('adding')});
}
clearInputs = () => {
this.setState({user: { name:'', email: '', phone: ''}});
}
showDetails = (i:number) => { //I need to populate the input fields based on the index of the object clicked.
console.log(i);
}
render(){
const { name, email, phone } = this.state.user;
<React.Fragment>
<form onSubmit={this.onAdd}>
<input type="text" value={name} onChange={(e:any) => this.handleChange(e)} name={"name"} />
<input type="text" value={email} onChange={(e:any) => this.handleChange(e)} name={"email"} />
<input type="text" value={phone} onChange={(e:any) => this.handleChange(e)} name={"phone"} />
<button type="submit">Add</button>
</form>
<ul>
{this.state.users.map((row:any ,index: number) =>
<li key={index}>
<a onClick={()=> this.showDetails(index)}><span>{row.name}</span></a> // on click of this,i need to display the values corresponding to this object in the above input fields
<i className="close far fa-times" onClick={() =>this.removeAccount(index)}/>
</li>
)}
</ul>
</React.Fragment>
}
}
Based on logic of the code showDetails should look like
showDetails = (i:number) => {
this.setState ({user: this.state.users.splice(i,1)});
console.log(i);
}
Just set user to the selected element of users array. React will do update and calls render() with updated data.
Also utilizing splice will remove currently editing user from array. THis follow logic of the code. After edit Add should be clicked to add modified user back to array. This may be not convenient, so you may consider adding editingIndex to state and specify which user object currently editing. In such case you'll have to save index of selected object in editingIndex. In handleChange you should check if some user object editing now and modify data not only in user property of state but in corresponding users array element
interface IState{
users : Account[];
user: Account;
editingIndex: number | null;
}
// In constructor
constructor(props:any){
super(props);
this.state= {
users: [],
user: {
name: '',
email: '',
phone: '',
},
editingIndex: null
}
}
showDetails = (i:number) => {
this.setState ({user: this.state.users[i], editingIndex: i});
console.log(i);
}
handleChange = ( event: React.ChangeEvent<HTMLInputElement>) => {
let user = {...this.state.user,
[event.currentTarget.name]:event.currentTarget.value};
this.setState({user});
// If we currently editing existing item, update it in array
if (this.state.editingIndex !== null) {
let users = [...this.state.users];
users[this.state.editingIndex] = user;
this.setState({users});
}
}
removeAccount = (i:number) => {
let users = [...this.state.users];
// If we're going to delete existing item which we've been editing, set editingIndex to null, to specify that editing ends
if (this.state.editingIndex === i)
this.setState({user: {name: '', email: '', phone: ''}, editingIndex: null});
users.splice(i,1);
this.setState({users},()=>{console.log('setting the data')});
}
onAdd = () => {
e.preventDefault();
// If we NOT editing, but adding new editingIndex will be null so add user to users array. If we editing existing element it's no need to add it once again.
if (this.state.editingIndex === null)
this.setState({ users: [...this.state.users, this.state.user] });
this.setState ({ editingIndex: null,
user: { name:'', email: '', phone: ''}
},()=>{console.log('adding')});
}
// render will have no change
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,
]
}));
}
I have a table, it's first three rows are hardcoded. After first three rows I want to add new objects in table. I write code for it but when I enter new data my old data is erase from table and new data is appear. I want it at its place and wants to add new data exactly after it. Here is the code of my state
constructor(props)
{
super(props)
{
this.state={
id:'',
name:'',
birth:'',
data:[
{
id:'1',
name:'Muhammad Ali jinnah',
dateofBirth:'1876'
},
{
id:'2',
name:'Allama Iqbal',
dateofBirth:'1877'
},
{
id:'3',
name:'Ahmad Bilal',
dateofBirth:'1992'
}
],
}
}
in that state i have array of objects i have hardcoded and state for data which i used to get data from my input box..input box are used to get data from user and add data in table and submit used to add data in table by using function..
here is code for my handle submit where i want to setstate for new object
handleSubmit(event) {
console.log('A ID:name and birth was submitted: ' + this.state.id,this.state.name,this.state.birth);
const { id, name, birth } = this.state;
const newdata = {
id: id,
name: name,
dateofBirth: birth
};
this.setState(prevState => ({
data: [prevState.data,newdata ]
}));
console.log("new array",this.state.data)
event.preventDefault();
}
I want to change its state but i also want my first three rows as i hardcoded
The error is occurring because you are not using the spread operator for inserting into the array.
When you use something like:
this.setState(prevState => ({
data: [prevState.data,newdata ] //This is wrong
}));
prevState.data becomes the first element of new array and new data becomes the second, instead you can use the spread operator for new array like:
this.setState(prevState => ({
data: [...prevState.data,newdata ]
}));
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
id: "",
name: "",
birth: "",
data: [
{
id: "1",
name: "Muhammad Ali jinnah",
dateofBirth: "1876"
},
{
id: "2",
name: "Allama Iqbal",
dateofBirth: "1877"
},
{
id: "3",
name: "Ahmad Bilal",
dateofBirth: "1992"
}
]
};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
console.log('A ID:name and birth was submitted: ' + this.state.id,this.state.name,this.state.birth);
const { id, name, birth } = this.state;
const newdata = {
id: id,
name: name,
dateofBirth: birth
};
this.setState(prevState => ({
data: [...prevState.data,newdata ]
}));
console.log("new array",this.state.data)
event.preventDefault();
}
render() {
return (
<main>
<form onSubmit={this.handleSubmit}>
<input name='id' type='number' value={this.state.id} onChange={this.handleInputChange} placeholder='ID' />
<input name='name' value={this.state.name} onChange={this.handleInputChange} placeholder='Name' />
<input name='birth' type='date' value={this.state.birth} onChange={this.handleInputChange} placeholder='Date of Birth' />
<button type='submit'>Add New</button>
</form>
<table className='content'>
<tbody>
{
this.state.data.map(item=>{
return (
<tr>
<td>{item.id}</td>
<td>{item.name}</td>
<td>{item.dateofBirth}</td>
</tr>
);
})
}
</tbody>
</table>
</main>
);
}
}
ReactDOM.render(<App />, 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>
Following is the pseudo code that should solve your issue.
const prevData = this.state.data;
// Make first 3 are not overridden
var preserved = prevData.splice(3)
preserved.push(...newData)
this.setState({ data : preserved});
If I'm understanding correctly... You should be able to create a render with 3 static rows, then dynamically append rows based on the object you have in state (this.state.data). See below for example.
render {
return (
<table>
<tr><td>sample1</td></tr>
<tr><td>sample2</td></tr>
<tr><td>sample3</td></tr>
{
this.state.data.map((dataElement) => {
<tr><td>{dataElement.name}</td></tr>
});
}
</table>
)
}
let temp=this.state.data;
temp=temp.push(newdata)
Add these lines into your handleSubmit() and set temp into your state by using setState method.
My Project is, an array of objects where i get only the names and render on screen of form 3 in 3, with button next and previous change the names, and can to filter for letters.
I would want add a new value, typped on input and clicked in the button add.
My code button:
addItem = () => {
const inValue = {
id: 0,
name: this.state.input
}
this.setState({
filtered: this.state.filtered.concat(inValue),
currentPage: 0
})
}
I would want the value inserted in the filtered array.
My code all:
import React, { Component } from 'react';
class App extends Component {
constructor() {
super();
const peoples =[{id:0, name:"Jean"},
{id:1, name:"Jaha"},
{id:2, name:"Rido"},
{id:3, name:"Ja"},
{id:4, name:"Letia"},
{id:5, name:"Di"},
{id:6, name:"Dane"},
{id:7, name:"Tamy"},
{id:8, name:"Tass"},
{id:9, name:"Ts"},
{id:10, name:"Abu"},
{id:11, name:"Ab"}];
this.state = {
elementsPerPage:3,
currentPage:0,
peoples,
input: "",
filtered: peoples,
teste: '',
};
}
getValueInput = (evt) => {
const inputValue = evt.target.value;
this.setState({ input: inputValue });
this.filterNames(inputValue);
}
filterNames = (inputValue)=> {
const { peoples } = this.state;
this.setState({
filtered: peoples.filter(item =>
item.name.includes(inputValue)),
currentPage:0
});
const Oi = this.state.filtered.map(item=>item.name);
if(Oi.length<=0){
alert('Você está adicionando um nome')
}
console.log(Oi)
}
elementsOnScreen = () => {
const {elementsPerPage, currentPage, filtered} = this.state;
return filtered
.map((item) => <li key={item.id}> {item.name} <button onClick={() => this.remove(item.name)}> Delete </button> </li>)
.slice(currentPage*elementsPerPage, currentPage*elementsPerPage + elementsPerPage);
if(this.state.filtered.length < 1){
this.setState({currentPage: this.state.currentPage - 1})
}
}
remove = (id) => {
console.log(this.state.filtered.length)
if(this.state.filtered.length < 0){
this.setState({currentPange: this.state.currenPage - 1})
}
this.setState({filtered: this.state.filtered.filter(item => item.name !== id) })
}
nextPage = () => {
console.log(this.state.filtered)
const {elementsPerPage, currentPage, filtered} = this.state;
if ((currentPage+1) * elementsPerPage < filtered.length){
this.setState({ currentPage: this.state.currentPage + 1 });
}
}
previousPage = () => {
const { currentPage } = this.state;
if(currentPage - 1 >= 0){
this.setState({ currentPage: this.state.currentPage - 1 });
}
}
addItem = () =>{
const inValue = {id:0 ,name: this.state.input}
this.setState({filtered: this.state.filtered.concat(inValue), currentPage: 0})
}
render() {
return (
<div>
<button onClick={this.addItem}> Add </button>
<input type="text" onChange={ this.getValueInput }></input>
<button onClick={this.previousPage}> Previous </button>
<button onClick={this.nextPage}> Next </button>
<h3>Current Page: {this.state.currentPage}</h3>
<ul>Names: {this.elementsOnScreen()}</ul>
</div>
);
}
}
export default App;
You would have the array of objects contained within your state, then use setState
this.state = {
elementsPerPage:3,
currentPage:0,
peoples,
input: "",
filtered: peoples,
teste: '',
peoples: [
{id:0, name:"Jean"},
{id:1, name:"Jaha"},
{id:2, name:"Rido"},
{id:3, name:"Ja"},
{id:4, name:"Letia"},
{id:5, name:"Di"},
{id:6, name:"Dane"},
{id:7, name:"Tamy"},
{id:8, name:"Tass"},
{id:9, name:"Ts"},
{id:10, name:"Abu"},
{id:11, name:"Ab"}];
};
To update the peoples array, you would first need to create a copy of the peoples array, modify the copy, then use setState to update.
let { peoples } = this.state;
peoples.push({ id:12, name:"Jean"})
this.setState({peoples: peoples})
Looks like you are already updating your state with the typed input.
So in your add button you can get the state value and push it to your people array. Something like this:
addItem = () => {
const { inputValue, people } = this.state;
if (!inputValue) return; // check if inputValue has any value
people.push({ id: people.length+1, name: inputValue )} // I don't recommend using sequencial ids like this, you'd better have a handler to generate it for you
this.setState({ people, inputValue: '' }); // update people and clear input
}
Hope it helps
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