How to update many input with one handler in ReactJs? - reactjs

I'm new at React, I have 3 years experience with Angular, which is why React seems strange to me. I created many input like this:
<tr>
<td>
<input value={this.state.x}
onChange={this.handleChange}/>
</td>
<td>
<input value={this.state.y}
onChange={this.handleChange}/>
</td>
<td>
<input value={this.state.z}
onChange={this.handleChange}/>
</td>
</tr>
from what I learn so far, I had to handle this input change event one by one for each <input> which I find laborious. Can I write one function to update all the input above ? Like:
handleChange = (event) => {
let obj = {};
obj[key] = event.target.value; // the key is my variable name, eg: x, y, z
this.setState(obj)
}
Does it possible to give several input a single handler function ? Thanks in advance

You can use event.target.name and event.target.value in order to update your component's as long as you set name property in each input:
this.setState({
[event.target.name]: event.target.value,
})
class App extends React.Component {
constructor() {
super()
this.state = {
x: '',
y: '',
z: '',
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(event) {
this.setState({
[event.target.name]: event.target.value,
})
}
render() {
return (
<div>
<table>
<tr>
<td>
<input name="x" value={this.state.x} onChange={this.handleChange} />
</td>
<td>
<input name="y" value={this.state.y} onChange={this.handleChange} />
</td>
<td>
<input name="z" value={this.state.z} onChange={this.handleChange} />
</td>
</tr>
</table>
<div>Current state: {JSON.stringify(this.state)}</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>

One other way to achieve this without giving names to your inputs is to bind names of the properties in your state as arguments in your change handlers. Then in the event handler you will receive the name of the property as the first argument and the event as the second argument. Here is an example:
class Test extends React.Component {
state = {
x: '',
y: '',
z: ''
}
handleChange(name, event) {
this.setState({ [name]: event.target.value });
}
render() {
return (<table>
<tr>
<td>
<input value={this.state.x}
onChange={this.handleChange.bind(this, 'x')} />
</td>
<td>
<input value={this.state.y}
onChange={this.handleChange.bind(this, 'y')} />
</td>
<td>
<input value={this.state.z}
onChange={this.handleChange.bind(this, 'z')} />
</td>
</tr>
</table>);
}
}
This way is probably less pretty than giving names to your inputs, but it works if you for some reason don't want to have names on the inputs.

handleChange = () => {
const {value, name} = event.target
this.setState({
[name] : value
})
}

Related

Weird return of data after splice and setstate

Here's simulate in codesandbox
https://codesandbox.io/embed/epic-nash-mxteu?fontsize=14&hidenavigation=1&theme=dark
I am having a weird behavior when I remove a row from the dynamic rows created.
I have removed the row with 2 b. As you can see the console log has the correct data while the UI showing wrong data. It means the function works well but somehow displaying incorrectly.
Anyone have idea why? Thanks in advance
Screenshots
Before remove row
After remove row
Source code
const [gavRows, setGAVRows] = useState([]);
const handleGAVAddRow = async () => {
try {
const item = {
gav_field: '',
gav_value: ''
};
setGAVRows([...gavRows, item]);
} catch (error) {
console.log('error', error)
}
};
const handleGAVRemoveSpecificRow = (idx) => {
console.log('idx', idx)
const tempRows = [...gavRows];
console.log('tempRows', tempRows)
tempRows.splice(idx, 1);
setGAVRows(tempRows)
};
const handleGAVChange = async (idx, e) => {
const { name, value } = e.target;
var tempRows = [...gavRows];
tempRows[idx][name] = value;
setGAVRows(tempRows)
};
<table className="customgav_section">
<tbody>
{
gavRows.map((item, idx) => {
console.log('map idx', idx, item)
return (
<tr key={idx}>
<td>
<Input type="text"
name="gav_field" id="gav_field"
value={gavRows[idx].field}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
<td>
<Input type="text"
name="gav_value" id="gav_value"
value={gavRows[idx].value}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
<td>
<Button outline color="danger" onClick={() => handleGAVRemoveSpecificRow(idx)}><FaMinus /></Button>
</td>
</tr>)
})
}
</tbody>
</table>
Your problem is that you are using the index of the array as the key.
Read why that is bad: https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318
A quick hack was assigning a random number as the key of each item in the gavRows and using that as the key of the element. See updated code: https://codesandbox.io/s/charming-bouman-03zn7
Also, the id of an element must be unique in the DOM so i removed those from the input elements.
Codesandbox
The problem is you put the wrong input value.
Remember the item object you set is:
const item = {
gav_field: "", //not field
gav_value: "" //not value
};
You should modify code from
<td>
<Input
type="text"
name="gav_field"
id="gav_field"
value={gavRows[idx].field}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
<td>
<Input
type="text"
name="gav_value"
id="gav_value"
value={gavRows[idx].value}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
To:
<td>
<Input
type="text"
name="gav_field"
id="gav_field"
value={gavRows[idx].gav_field}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>
<td>
<Input
type="text"
name="gav_value"
id="gav_value"
value={gavRows[idx].gav_value}
onChange={(e) => handleGAVChange(idx, e)}
/>
</td>

Append data as table in react js

I have an application what takes the date from inputs and saving it, the data are apendded bellow, something like todo list. But now i have an issue trying to display the data, because i want to display it in a specific order as table row, but now the data is not showing properly, because i want to have Name under Name, Old under old, delete button under delete Text, ad edit button under edit text.
How to do this?
link to my application: https://codesandbox.io/s/nifty-moore-g86kd
There are a few issues in your code.
You don't have any state to keep track of the added users
On a form submit instead of updating the data you're trying to directly update the DOM with submitted data. Which is not the right way to do things in react.
import React, { useState, useEffect } from "react";
export default function App() {
const [user, setUser] = useState({
name: "",
old: ""
});
// A new state to keep track of the added users
const [users, setUsers] = useState([]);
const changeUser = e => {
const v = e.target.value;
setUser({
...user,
[e.target.name]: v
});
};
// On form submit, all you need to do is to update the users state
// Then render will take care of the rest
const submitForm = e => {
e.preventDefault();
setUsers([...users, user]);
};
// This is how in react we update the content
// Whenever, there is a change in state, this will get called and content will be updated
// Ps: It's being called in the render
const renderBody = () => {
const content = [];
users.map(item => {
content.push(
<tr>
<td>{item.name}</td>
<td>{item.old}</td>
<td>Delete btn</td>
<td>Edit btn</td>
</tr>
);
});
return content;
};
return (
<div className="to-do">
<form action="" onSubmit={submitForm}>
<label htmlFor="">
Name
<input
name="name"
onChange={changeUser}
value={user.name}
type="text"
/>
</label>
<label htmlFor="yes">
Old Yes
<input
id="yes"
name="old"
onChange={changeUser}
value="yes"
type="radio"
/>
</label>
<label htmlFor="no">
Old No
<input
id="no"
name="old"
onChange={changeUser}
value="no"
type="radio"
/>
</label>
<input value={user.old} type="submit" value="SUBMIT" />
</form>
<div className="res">
<table>
<tr>
<th>Name</th>
<th>OLD</th>
<th>DELETE</th>
<th>Edit</th>
</tr>
<tr id="res" />
{renderBody()}
</table>
</div>
</div>
);
}
<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>
So what you need
State for users to keep track of the added users
On form submit, a trigger to update that users state
A loop, to iterate over users array and return table rows with content

React - List?Dictionary?State? Or am i missing something

Let's start off with the problem I'm having and telling you guys what I would like to achieve.
First of all, I'm getting this error
Warning: A component is changing a controlled input of type text to be uncontrolled.
Input elements should not switch from controlled to uncontrolled (or vice versa).
Decide between using a controlled or uncontrolled input element for the lifetime of the component.
My goal is to save data first to the state. Or I should use List, or dictionary? This is where I'm stuck. I will post my code here also for you to check what I'm doing wrong or what should I do differently.
import React from 'react'
import './TableData.css'
class TableData extends React.Component{
constructor(props){
super(props)
this.state = {
rows:[{service: '',
quantity: '',
price: '',
sum: ''}]
}
this.handleChange = this.handleChange.bind(this)
this.handleAddRow = this.handleAddRow.bind(this)
this.handleRemoveRow = this.handleRemoveRow.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange = idx => event => {
var rows = [...this.state.rows]
rows[idx] = {
[event.target.name]: event.target.value
}
this.setState({
rows
})
var data = this.state.rows
console.log("Log me", data)
}
handleAddRow = () => {
var item = {
service: '',
quantity: '',
price: '',
sum: ''
}
this.setState({
rows: [...this.state.rows, item]
})
}
handleRemoveRow = () => {
this.setState({
rows: this.state.rows.slice(0, -1)
})
}
handleSubmit = (event) => {
event.preventDefault()
var tableData = this.state.rows
console.log("Final data is:", tableData)
}
render() {
return (
<div className="tablePos container" >
<form onSubmit={this.handleSubmit}>
<div className="row">
<table id="tab_logic">
<thead className="tableBackground">
<tr>
<th className="col-md-auto"> Service </th>
<th className="col col-lg-2"> Quantity </th>
<th className="col col-lg-2"> Price </th>
<th className="col col-lg-2"> Sum </th>
</tr>
</thead>
<tbody>
{this.state.rows.map((item, idx) => (
<tr key={idx}>
<td>
<input className="form-control" type="text" name="service" placeholder="Ex: Cloud Service" value={this.state.rows[idx].service} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="quantity" placeholder="Ex: 2 Month" value={this.state.rows[idx].quantity} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="price" placeholder="Ex: 75.00" value={this.state.rows[idx].price} onChange={this.handleChange(idx)}/>
</td>
<td>
<input className="form-control" type="text" name="sum" placeholder="Ex: 150.00" value={this.state.rows[idx].sum} onChange={this.handleChange(idx)} />
</td>
</tr>
))}
</tbody>
</table>
</div>
<button>Send Data!</button>
</form>
<button onClick={this.handleAddRow} className="btn btn-success">Add Row</button>
<button onClick={this.handleRemoveRow} className="btn btn-danger">Delete Last Row</button>
</div>
);
}
}
export default TableData
So basically it creates 4 input boxes and then you can write in and if you are done you click Send Data it saves it to state or add new row and then it will add new row for you to input data. What I do get is the following from that code.
Console log picture of the data
It only saves the last input field data when I click send data not all of them.
Sorry about my messy explanation but I hope you did understand my problem and thank you for your replies!
while assigning the values inside onChange. You are spreading the array as needed. But you have to spread the object too.. Otherwise, it will just assign the last-changed-input-field-value to the object.
rows[idx] = {
...this.state.rows[idx],
[event.target.name]: event.target.value
};
You can find my code below.
https://codesandbox.io/s/small-dew-wjqqi

How to set State when value is derived from Array in React App

I have a react App, which renders a table from an array in the state.
I am trying to have an "EDIT MODE" which transforms some fields into textbox's so that I can update each row in the table.
I'm not sure how I can handle the onChange event when the value is derived from an element inside an array in the state.
here is my code, I have explained the problem in the comments:
class AddProjectType extends Component {
constructor(props) {
super(props);
this.state = {
editMode: false
}
this.changeEditMode = this.changeEditMode.bind(this); //Bind This so I can use this.setState
this.changeProjectName = this.changeProjectName.bind(this); //Bind This so I can use this.setState
}
componentDidMount() {
this.props.fetchProjectTypes(); //This fetched the table from the nodejs server
}
changeEditMode() {
this.setState({ editMode: !this.state.editMode }); //Convert into edit mode and change rows in the table to inputs
}
changeProjectName(event){
//this.setState - Unsure how to set state of a particular array HERE IS THE PROBLEM
}
render() {
if (!this.props.projectTypes) {
return (
<CenterLoader /> //loading the table from server - show loader
)
}
else
return (
<div className="container">
<table className="ProjectType-Table">
<tbody>
<tr>
<th>
Id
</th>
<th>
Project Type
</th>
<th>
</th>
</tr>
{this.props.projectTypes.map((projectType, i) => { //RENDER ALL ROWS
return (
<tr key={i}>
<td>
{projectType._id}
</td>
<td>
{this.state.editMode ?
<input type="text" className="browser-default" defaultValue={projectType.name} onChange={this.changeProjectName}/> //On change, I need to save back to the state this value
:
projectType.name
}
</td>
<td>
<button className="btn btn-small mr-1" onClick={this.changeEditMode}>Edit</button>
<button className="btn btn-small">Delete</button>
</td>
</tr>
)
}
)}
</tbody>
</table>
</div>
);
}
}
function mapStateToProps(state) {
return { projectTypes: state.quizz.projectTypes };
}
export default connect(mapStateToProps, actions)(AddProjectType);
Attach data-idx={i} in the input like this,
<input type="text" className="browser-default" defaultValue={projectType.name} onChange={this.changeProjectName} data-idx={i}/>
In your changeProjectName(event) handler,
var index = event.target.dataset.idx; // Here you will get index

Clear input text after click event

The issue I am having is after entering text for the first time, I then click Add and the input text box gets cleared. However when I start to enter text in again, the input text will not let me enter any text apart from the first letter. I'm not sure why it's doing this.
var FormTextBox = React.createClass({
handleOnBlur: function (e) {
this.props.onBlur(e.target.value);
},
render: function () {
return (
<input value={this.props.value} key={this.props.fid} type="text" onBlur={this.handleOnBlur} />
)
}
});
var TestFormTextBox = React.createClass({
getInitialState: function (e) {
return {
value: ''
}
},
handleOnAdd: function (e) {
this.setState({ value: '' });
},
handleTextInfo: function (value) {
this.setState({ value: value });
},
render: function () {
return (
<div>
<table>
<tbody>
<tr>
<td>Details</td>
<td></td>
</tr>
<tr>
<td><FormTextBox value={this.state.value} fid={1} onBlur={this.handleTextInfo} /></td>
<td><button onClick={this.handleOnAdd}>Add</button></td>
</tr>
</tbody>
</table>
</div>
)
}
});
I am surprised that this even works the first time. With controlled components in react (ones where you are setting the value like you are with your input). you need to update the value whenever the user changes the text (with the onChange() event).
I made a JS fiddle here with your original code and you can see you can't even update the value in the input. In order to get it to update you need to replace the onBlur event with an onChange event like this JS fiddle. Hope that helps!
As mentioned here (https://facebook.github.io/react/docs/forms.html#controlled-components),
A controlled has a value prop. Rendering a controlled will reflect the value of the value prop.
User input will have no effect on the rendered element because React has declared the value to be Hello!. To update the value in response to user input, you could use the onChange event
You need to either change onBlur to onChange, or use defaultValue instead of value. e.g.
<input defaultValue={this.props.value} key={this.props.fid} type="text" onBlur={this.handleOnBlur} />
You need to change the value of the state variable as soon as you are typing the input value because that is what you are providing the input if you don't change it then the input wont show the updated value. In order to do this you need to use the onChange event everywhere.
var FormTextBox = React.createClass({
handleOnChange: function (e) {
this.props.onChange(e.target.value);
},
render: function () {
return (
<input value={this.props.value} key={this.props.fid} type="text" onChange={this.handleOnChange} />
)
}
});
var TestFormTextBox = React.createClass({
getInitialState: function (e) {
return {
value: ''
}
},
handleOnAdd: function (e) {
this.setState({ value: '' });
},
handleTextInfo: function (value) {
this.setState({ value: value });
},
render: function () {
return (
<div>
<table>
<tbody>
<tr>
<td>Details</td>
<td></td>
</tr>
<tr>
<td><FormTextBox value={this.state.value} fid={1} onChange={this.handleTextInfo} /></td>
<td><button onClick={this.handleOnAdd}>Add</button></td>
</tr>
</tbody>
</table>
</div>
)
}
});
ReactDOM.render(<TestFormTextBox />, document.getElementById('app'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.min.js"></script>
<div id="app"></div>

Resources