How to set multiple object value with special key on react js - javascript

i need your help to set the new value in multiple object. I had object like this:
constructor(props){
this.state = {
objData : [{
score:{q1:null,q2:null,q3:null},
data:{id:123, name:"Steven CHS"}
},
{
score:{q1:null,q2:null,q3:null},
data:{id:124, name:"Christian"}
},
]
}
}
and i would like to change the value by key q1,q2, anda q3. When i tried to use destructor like this, it's not working.
const handleChangeScore = (e,type,id) =>{
const cScore = e.target.value;
this.setState((state) => {
return {
objData: state.objData.map((item) => {
if(item.data.id !== e.target.name) return item;
else return {...item.score.q1, cScore};
})
};
});
}
this is the JSX where handleChangeScore() is used:
this.state.objData .map((v,index)=>(
<tr>
<td>{index+1}</td>
<td>{v.data.id} - {v.data.name}</td>
<td><input type="text" name={v.data.id} className="form-control" onChange={(e)=>handleChangeScore(e,"q1",v.data.id)} defaultValue={(v.score.q1) ? v.score.q1 : 0} /></td>
<td><input type="text" name={v.data.id} className="form-control" onChange={(e)=>handleChangeScore(e,"q2",v.data.id)} defaultValue={(v.score.q2) ? v.score.q2: 0} /></td>
</tr>
))
The problem is when i use destructor to change the value on multiple object with specific key its not work or not change the value. Can anyone help me to fix my code ? Here's the full code in codesanbox

Few things to be fixed
Your item.data.id is number and e.target.name is string. To compare them without type comparison use != instead of !==.
The else block should be corrected as below.
handleChangeScore = (e, type, id) => {
const cScore = e.target.value;
this.setState((state) => {
return {
...state,
objData: state.objData.map((item) => {
if (item.data.id != e.target.name) return item;
else {
return { ...item, score: { ...item.score, [type]: cScore } };
}
})
};
});
};
Code Sandbox

Try below code no need to use map(). just pass the index in this.handleChangeScore() function !
And put handleChangeScore() this function in outside of render() !
export class App extends Component {
constructor(props) {
super(props);
this.state = {
objData: [
{
score: { q1: null, q2: null, q3: null },
data: { id: 123, name: "Steven CHS" }
},
{
score: { q1: null, q2: null, q3: null },
data: { id: 124, name: "Christian" }
}
]
};
}
handleChangeScore = (value, index, childObj) => {
this.state.objData[index].score[childObj] = value;
this.setState(this.state);
};
render() {
return (
<div className="App">
<table border="1">
{this.state.objData.map((v, index) => (
<tr>
<td>{index + 1}</td>
<td>
{v.data.id} - {v.data.name}
</td>
<td>
<input
type="text"
name={v.data.id}
className="form-control"
onChange={(e) => this.handleChangeScore(e.target.value, index, "q1")}
defaultValue={v.score.q1 ? v.score.q1 : 0}
/>
</td>
<td>
<input
type="text"
name={v.data.id}
className="form-control"
onChange={(e) => this.handleChangeScore(e.target.value, index, "q2")}
defaultValue={v.score.q2 ? v.score.q2 : 0}
/>
</td>
</tr>
))}
</table>
</div>
);
}
}
export default App;

Related

React, not able to change/editing anything in textbox?

I have data that I am getting from api. I am filling the data in the array. I am able to display the data properly. I am not able to make any change in the DisplayOrder Textboxes. Please find my data and code. What to do so that textbox is editable?
[{"featureID ":"10,"descrText":"Test1","featureText":"<h1>Feature1</h1>","displayOrder":"1},
{"featureID ":"11,"descrText":"Test2","featureText":"<h1>Feature2</h1>","displayOrder":"2},
{"featureID ":"12,"descrText":"Test3","featureText":"<h1>Feature3</h1>","displayOrder":"3}]
import React from "react";
import ReactHtmlParser from 'react-html-parser';
export class EditFeatures extends React.Component {
constructor(props) {
super(props);
this.state = {
FeatureID: "",
DisplayOrder: "",
DescrText: "",
FeatureText: "",
Feature:[],
};
}
componentDidMount() {
this.DisplayFeatures();
}
DisplayFeatures() {
fetch(REQUEST_URL, { "Content-Type": "application/xml; charset=utf-8" })
.then(response => response.json())
.then((data) => {
this.setState({
Feature:data,
loading: false
})
})
}
render() {
return (
<div>
<form>
<table width="100%" cellpadding="0" cellpadding="0" border="1"><tbody>
{this.state.Feature.map((item, index) => {
return [
<tr key={item.FeatureID}>
<td width="50px">{item.featureID}
<input type="text" name=="DisplayOrder" size="5" maxLength="10" value={item.displayOrder}
onChange={(ev) => this.setState({ DisplayOrder: ev.target.value })} /></td>
<td align="left"><font size="3">{ReactHtmlParser(item.descrText)}</font></td>
<td align="left">{ReactHtmlParser(item.featureText)}</td></tr>
];})}
</tbody></table>
<button type="submit" name="submit">Update</button>
</form>
</div>
);
}
}
export default EditFeatures;
So when you're rendering out the value of display order, you're using the item.displayOrder as the inputs value; but in your onChange event, you're updating this.state.DisplayOrder. For you to be able to update the value, you would need to update item.displayOrder value, not the states; which would be something like
this.setState(prevState => ({
Feature: prevState.Feature.map(feature => {
if (feature.featureID === IdOfFeatureYouWantToUpdate) {
return { ...feature, { displayOrder: newValue }};
}
})
}));
If you're wanting to use the states DisplayOrder, then for the value of the input you need to use this.state.DisplayOrder instead of item.displayOrder.
You are not updating the same state, This might help
<input type="text" id={item.FeatureID} name="DisplayOrder" size="5" maxLength="10" value={item.displayOrder}
onChange={
(ev) =>{
const newFeature = this.state.Feature.map(f => {
if (f.FeatureID == ev.target.id) {
f.displayOrder = ev.target.value;
}
return f;
});
this.setState({ Feature: newFeature })
}
} />

Problem with alert after adding new value to the array in React.js

During the React.js course I'm doing, I was tasked with making a simple fortune-teller app. Theoretically, everything works as planned, but I did the task differently than the tutor. Instead of a simple fortune-telling table, I've created an array of objects, each with its id and 'omen'. The problem arose when after adding a new 'omen' an alert should be displayed that gives the current content of 'omens' in state. Only the previous values appear, without the added value. I will be grateful for the hints. In the original design, this problem does not occur, although it is very similar.
class Draw extends React.Component {
state = {
index: "",
value: "",
omens: [
{ id: 1, omen: "Hard work pays off" },
{ id: 2, omen: "You will be rich" },
{ id: 3, omen: "Be kind to others" },
],
};
handleDrawOmen = () => {
const index = Math.floor(Math.random() * this.state.omens.length + 1);
this.setState({
index: index,
});
};
showOmen = () => {
let omens = this.state.omens;
omens = omens.filter((omen) => omen.id === this.state.index);
return omens.map((omen) => (
<h1 id={omen.id} key={omen.id}>
{omen.omen}
</h1>
));
};
handleInputChange = (e) => {
this.setState({
value: e.target.value,
});
};
handleAddOmen = () => {
if (this.state.value === "") {
return alert("Enter some omen!");
}
const omens = this.state.omens.concat({
id: this.state.omens.length + 1,
omen: this.state.value,
});
this.setState({
omens,
value: "",
});
console.log(this.state.omens);
alert(
`Omen added. Actual omens: ${this.state.omens.map(
(omen) => omen.omen
)}`
);
};
render() {
return (
<div>
<button onClick={this.handleDrawOmen}>Show omen</button>
<br />
<input
placeholder="Write your own omen..."
value={this.state.value}
onChange={this.handleInputChange}
/>
<button onClick={this.handleAddOmen}>Add omen</button>
{this.showOmen()}
</div>
);
}
}
ReactDOM.render(<Draw />, document.getElementById("root"));
The state object is immutable. So you need to create your new array and apply it afterwards:
const omens = [
...this.state.omens,
{
id: this.state.omens.length + 1,
omen: this.state.value,
}
]
also setState is async so you need to wait until it finished:
this.setState({
omens,
value: "",
}, () => {
alert(
`Omen added. Actual omens: ${this.state.omens.map(
(omen) => omen.omen
)}`
)
https://reactjs.org/docs/react-component.html#setstate

Using react, how would I hide a table row on click?

Hey guys I am using this table to display data and I added a button to each row. How would I be able to hide a row when I click the hide button next to it?
I am aware of a way to do within html elements but not sure how to hide a particular row within a table thats within a loop
Can anyone show me how to accomplish this?
Thank you
import React, { Component } from 'react'
class Table extends Component {
constructor(props) {
super(props) //since we are extending class Table so we have to use super in order to override Component class constructor
this.state = { //state is by default an object
students: [
{ id: 1, name: 'Wasif', age: 21, email: 'wasif#email.com' },
{ id: 2, name: 'Ali', age: 19, email: 'ali#email.com' },
{ id: 3, name: 'Saad', age: 16, email: 'saad#email.com' },
{ id: 4, name: 'Asad', age: 25, email: 'asad#email.com' }
]
}
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email } = student //destructuring
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><button>HIDE</button></td>
</tr>
)
})
}
renderTableHeader() {
let header = Object.keys(this.state.students[0])
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>
})
}
render() { //Whenever our class runs, render method will be called automatically, it may have already defined in the constructor behind the scene.
return (
<div>
<h1 id='title'>React Dynamic Table</h1>
<table id='students'>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
)
}
}
export default Table
You could add an onClick handler to the button that adds a property that determines the student should be hidden or not.
Notice the onClick={() => this.hideRow(id)} below.
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, isHidden } = student; //destructuring
// isHidden will default to undefined if not found on the student object
// user is hidden
if (isHidden === true) {
return null;
}
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td>
<button onClick={() => this.hideRow(id)}>HIDE</button>
</td>
</tr>
);
});
}
The hideRow method will accept a student id and will add an isHidden: true attribute to the student with that id.
hideRow(id) {
const students = this.state.students.map((student) => {
// not same id? leave as is
if (student.id !== id) {
return student;
}
return { ...student, isHidden: true };
});
this.setState({ students });
}
Now you don't want to display the isHidden column, so you have to update renderTableHeader method to skip that.
renderTableHeader() {
let header = Object.keys(this.state.students[0]);
return header.map((key, index) => {
// notice this
if (key === "isHidden") {
return null;
}
return <th key={index}>{key.toUpperCase()}</th>;
});
}
Add a isVisible key in all objects like
students: [
{ id: 1, name: 'Wasif', age: 21, email: 'wasif#email.com', isVisible: true },
{ id: 2, name: 'Ali', age: 19, email: 'ali#email.com', isVisible: true },
{ id: 3, name: 'Saad', age: 16, email: 'saad#email.com', isVisible: true },
{ id: 4, name: 'Asad', age: 25, email: 'asad#email.com', isVisible: true }
]
Then in your render row function do this
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, isVisible } = student
return isVisible ? (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<td><button>HIDE</button></td>
</tr>
) : null
})
On button/row click update state.
Try this code
import React, { Component } from "react";
class Table extends Component {
constructor(props) {
super(props); //since we are extending class Table so we have to use super in order to override Component class constructor
this.state = {
//state is by default an object
students: [
{ id: 1, name: "Wasif", age: 21, email: "wasif#email.com", toggle: true},
{ id: 2, name: "Ali", age: 19, email: "ali#email.com", toggle: true },
{ id: 3, name: "Saad", age: 16, email: "saad#email.com", toggle: true},
{ id: 4, name: "Asad", age: 25, email: "asad#email.com", toggle: true }
]
};
}
handleClick(index) {
let students = [...this.state.students];
students[index].toggle = !students[index].toggle;
this.setState({ students });
}
renderTableData() {
return this.state.students.map((student, index) => {
const { id, name, age, email, toggle } = student; //destructuring
if (toggle) {
return (
<tr key={id}>
<td>{id}</td>
<td>{name}</td>
<td>{age}</td>
<td>{email}</td>
<`td`>
<button
value={index}
onClick={(e) => this.handleClick(e.target.value)}
>
Hide
</button>
</td>
</tr>
);
} else {
return null;
}
});
}
renderTableHeader() {
let header = Object.keys(this.state.students[0]);
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>;
});
}
render() {
//Whenever our class runs, render method will be called automatically, it may have already defined in the constructor behind the scene.
return (
<div>
<h1 id="title">React Dynamic Table</h1>
<table id="students">
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
);
}
}
export default Table;
Follow these steps:
Put an onclick on the button
Pass the array as props to the component
On the next component display the array
Add the onclick method to it which is also passed as a props from the main component(Pass id as a parameter)
In the method use a filter array to remove the row of your choice when you click it.
The code is as follow:
https://codesandbox.io/s/modern-tdd-mlmzl?file=/src/components/Table.js

Make table with filter in react failed

I'm able to make the filter works but simply use the filter of es6, but then the problem is I don't know how to reset the state back to the original source.
Usually the data source is an API calls but it possible to make avoid api call when the user deleted the value from the filter input?
const data = [
{
Id: "1",
FirstName: "Luke",
LastName: "Skywalker"
},
{
Id: "2",
FirstName: "Darth",
LastName: "Vader"
},
{
Id: "3",
FirstName: "Leia",
LastName: "Organa"
},
{
Id: "4",
FirstName: "Owen",
LastName: "Lars"
}
];
class App extends React.Component {
constructor() {
super()
this.state = {
data: data
}
}
filterId(e) {
const Id = e.target.value
if (Id) {
this.setState({
data: this.state.data.filter(v => v.Id === Id),
})
} else {
this.setState({
data
})
}
}
render() {
return (
<div style={styles}>
<table>
<th>Id <input type="number" onChange={e => this.filterId(e)} /></th>
<th>Name<input /></th>
{this.state.data.map((o, i) => {
return (
<tr>
<td>{o.Id}</td>
<td>{o.FirstName}</td>
</tr>
)
})}
</table>
</div>
);
}
}
https://codesandbox.io/s/oo22451v25
You may either store filtered data in separate variable or do data filtering dynamically in render function, like this.state.data.filter(v => v.Id === this.state.Id).map(...).
First up: You're going to want to filter using includes() for usability. Otherwise things won't match until they're 100% identical. This will matter once you start to deal with fields longer than one digit.
Secondly: Filtration via the render() method will allow you to search more robustly, ie allow you to backspace to unfilter, as this.state.data will remain pristine.
See below for a practical example.
// Data.
const data = [
{
Id: "1",
FirstName: "Luke",
LastName: "Skywalker"
},
{
Id: "2",
FirstName: "Darth",
LastName: "Vader"
},
{
Id: "3",
FirstName: "Leia",
LastName: "Organa"
},
{
Id: "4",
FirstName: "Owen",
LastName: "Lars"
}
]
// Filter.
class Filter extends React.Component {
// Constructor.
constructor(props) {
super(props)
this.state = {data, query: ''}
}
// Render.
render() {
return (
<div>
<table>
<tr>
<input placeholder="Query" type="number" value={this.state.query} onChange={event => this.setState({query: event.target.value})} />
</tr>
<th>Id</th>
<th>Name</th>
{this.state.data.filter((point) => point.Id.includes(this.state.query)).map((o, i) => {
return (
<tr>
<td>{o.Id}</td>
<td>{o.FirstName}</td>
</tr>
)
})}
</table>
</div>
)
}
}
ReactDOM.render(<Filter/>, document.querySelector('#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>
There are several thing you could do, but I would advice not to change the original data stored in the state, but rather add a new state property which holds the data when the original data is filtered.
You could then assign this new state object to a variable and output it this way.
It could then look somethiing like this:
const data = [
{
id: '1',
firstName: 'Luke',
LastName: 'Skywalker',
},
{
id: '2',
firstName: 'Darth',
LastName: 'Vader',
},
{
id: '3',
firstName: 'Leia',
LastName: 'Organa',
},
{
id: '4',
firstName: 'Owen',
LastName: 'Lars',
},
];
class App extends React.Component {
constructor() {
super();
this.state = {
data: data,
filteredData: null,
};
this.filterId = this.filterId.bind(this);
}
filterId(e) {
const id = e.target.value;
if (id) {
this.setState({
filteredData: this.state.data.filter(v => v.id === id),
});
} else {
this.setState({
filteredData: null,
});
}
}
render() {
const dataOutput = this.state.filteredData || this.state.data;
return (
<div>
<table>
<th>
id <input type="number" onChange={e => this.filterId(e)} />
</th>
<th>
Name<input />
</th>
{dataOutput.map((o, i) => {
return (
<tr key={o.id}>
<td>{o.id}</td>
<td>{o.firstName}</td>
</tr>
);
})}
</table>
</div>
);
}
}
Oh, and one more thing: Use camelCase. Capital letters at the beginning should only be used when declaring classes.

In React, how to bind an input's value when rendering a list of inputs?

I'm rendering a list of inputs and I want to bind each input's value to a link's href. My current attempt renders https://twitter.com/intent/tweet?text=undefined:
class App extends React.Component {
tweets = [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
];
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => tweet.text = e.target.value} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.text}`}>Tweet</a>
</div>
)}
</div>
);
}
}
This probably needs to involve setState but I have no idea how to achieve that when rendering a list. I've tried to do some research on this but didn't found anything helpful.
JSFiddle: https://jsfiddle.net/nunoarruda/u5c21wj9/3/
Any ideas?
You can move the tweets variable to the state to maintain consistency in that array.
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
};
setTweets = index => e => {
const { tweets } = this.state
tweets[index].text = e.target.value
this.setState({ tweets })
}
render() {
const { tweets } = this.state
return (
<div>
{tweets.map((tweet, index) =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={this.setTweets(index)} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.text}`}>Tweet</a>
</div>
)}
</div>
);
}
}
Updated Jsfiddle: https://jsfiddle.net/u5c21wj9/6/
You can reach the desired result using state.
return (
<div>
{tweets.map(({ id, link }) =>
<div key={id}>
<input type="text" placeholder="text" onChange={({ target }) => this.setState({ [id]: target.value })} />
<a href={`https://twitter.com/intent/tweet?text=${this.state[id] || link}`}>Tweet</a>
</div>
)}
</div>
);
Note: I would move tweets outside the component and implement few ES6 features.
Updated Jsfiddle: https://jsfiddle.net/u5c21wj9/7/
You really should use a state here and make your tweets variable be part of it. To do that, add a constructor:
constructor() {
super();
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
}
Then you need to mutate each linkwhenever you type in one of the inputs. There are a few pitfalls here, so let me go through them one-by-one:
changeTweet = (id, e) => {
let arr = this.state.tweets.slice();
let index = arr.findIndex(i => i.id === id);
let obj = Object.assign({}, arr[index]);
obj.link = e.target.value;
arr[index] = obj;
this.setState({tweets: arr});
}
First, you need to create a copy of your state variable. This gives you something to work with, without mutating the state directly which is anti-pattern. This can be done with slice().
Since you are sending in the id of the object to modify, we need to find it in our array (in case the items are unordered). This is done with findIndex(). You might want to handle the scenario in which such index is not found (I have not done that).
Now we know where in the array the object with the given id key is. Now, create a copy of that item (which is an object). This is also to prevent mutating the state directly. Do this with Object.assign().
Now change the link to the input value we typed in. Replace the old item object with the new one (obj) and replace the old tweets array with the new one (arr).
Here's the full example:
class App extends React.Component {
constructor() {
super();
this.state = {
tweets: [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
]
};
}
changeTweet = (id, e) => {
let arr = this.state.tweets.slice();
let index = arr.findIndex(i => i.id === id);
let obj = Object.assign({}, arr[index]);
obj.link = e.target.value;
arr[index] = obj;
this.setState({tweets: arr});
}
render() {
return (
<div>
{this.state.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={(e) => this.changeTweet(tweet.id, e)} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.link}`}>Tweet</a>
</div>
)}
</div>
);
}
}
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>
You need to save the text from the input in the state (using setState), not in the tweets array. Then you can render it getting the text from the state.
class App extends React.Component {
tweets = [
{ id: 1, link: 'example.com' },
{ id: 2, link: 'example2.com' }
];
state = {
tweetsText :{}
}
handleTextChange = (event, tweetId) => {
const tweetsTextCopy = Object.assign({}, this.state.tweetsText)
tweetsTextCopy[tweetId] = event.target.value
this.setState({tweetsText: tweetsTextCopy})
}
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => this.handleTextChange(e, tweet.id)} />
<a href={`https://twitter.com/intent/tweet?text=${this.state.tweetsText[tweet.id]}`}>Tweet</a>
</div>
)}
</div>
);
}
}
Links info is in the link property of your tweets array. The property text is not defined.
So, your render function should look like this
render() {
return (
<div>
{this.tweets.map(tweet =>
<div key={tweet.id}>
<input type="text" placeholder="text" onChange={e => tweet.text= e.target.value} />
<a href={`https://twitter.com/intent/tweet?text=${tweet.link}`}>Tweet</a>
</div>
)}
</div>
);
}

Categories