use a single handler for multiple inputs onChange events - javascript

I have bunch of inputs and I do not want to have multiple handlers like
handleInput1(){},handleInput2(){} and so on.
But I have difficulties producing below array of object
[{
name: 3,
value: 1000
},{
name: 5,
value: 1000
}]
how can I by using listen to only one handler and use setState in react?
http://jsbin.com/godesacici/edit?js,console,output

You can try to do it the follwoing way by distinguishing the different inputs by the name attribute and storing the result in the state
class HelloWorldComponent extends React.Component {
constructor(){
super();
this.state = {
result: []
}
}
handleInput(e) {
console.log(e.target.value);
var result = [...this.state.result];
var idx = result.map(function(val){ return val.name}).indexOf(e.target.name);
if(idx > -1) {
result[idx].value=e.target.value;
} else {
result.push({name: e.target.name, value:e.target.value});
}
this.setState({result})
}
handleClick() {
console.log(this.state.result);
}
render() {
return (
<div>
<div><input type="number" name="4" onChange={this.handleInput.bind(this)}/></div>
<div><input type="number" name="3" onChange={this.handleInput.bind(this)}/></div>
<div><input type="number" name="5" onChange={this.handleInput.bind(this)}/></div>
<button onClick={this.handleClick.bind(this)}>submit</button>
</div>
);
}
}
React.render(
<HelloWorldComponent name="Joe Schmoe"/>,
document.getElementById('react_example')
);
JSBIN

So you can be explicit and bind the key string onto a single handler function like so:
_handleInput(key, val) {
let { ..state } = this.state;
state[key] = val;
this.setState(state);
}
render() {
return <div>
<input
onChange={this.handleInput.bind(null, key1)}
value={this.state.key1} />
<input
onChange={this.handleInput.bind(null, key2)}
value={this.state.key2} />
</div>
}

Since it's an an array you want to modify you can use array indices. Suppose the initial state is this.
this.state = {
array: [{
name: 3,
value: 1000
},{
name: 5,
value: 1000
}]
}
Then the inputs can be like this (for the one with name 3 which has index 0)
<input value={this.state.array[0].value} onChange={this.handleChange.bind(this,0)}/>
So the value it will display is for the 1st element in the array (with index 0) and the handleChange binds to the event as well as pass the index 0.
handleChange(index,event){
this.setState((prevState) => ({array:[
...prevState.array.slice(0, index),
Object.assign({},prevState.array[index],{value:event.target.value}),
...prevState.array.slice(index + 1)
]}))
}
Ok so this might seem a little complicate but let me try to explain here. So the handleChange method takes two parameters - the event corresponding to the input text and the index which is the array index of the element in the state array (0 in our example). So in this.setState we have taken the prevState and used a bit of splicing. ...prevState.array.slice(0,index) corresponds to all elements of the array before the one we are modifying. ...prevState.slice(index+1) corresponds to all those after. So we take these two sets and join them with the modified element in between. The Object.assign() corresponds to the modified element. What it is doing is taking the prevState.array[index] which is the element we are modifying and setting it's value to event.target.value corresponding to the text.

If you change your state model to have a key per form element and use some nice-to-haves like arrow functions to capture variable scope in a cleaner syntax, you can simplify things:
class HelloWorldComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
"3": {},
"4": {},
"5": {}
}
}
handleInput(name, value) {
this.setState({
[name]: {
name: name,
value: value
}
});
}
handleClick() {
console.log(this.state);
}
render() {
return (
<div>
<div><input type="number" value={this.state["3"].value} onChange={(e) => this.handleInput(3, e.target.value)}/></div>
<div><input type="number" value={this.state["4"].value} onChange={(e) => this.handleInput(4, e.target.value)}/></div>
<div><input type="number" value={this.state["5"].value} onChange={(e) => this.handleInput(5, e.target.value)}/></div>
<button onClick={(e) => this.handleClick()}>submit</button>
</div>
);
}
}
React.render(
<HelloWorldComponent name="Joe Schmoe"/>,
document.getElementById('react_example')
);
Having your state be an array of values not keyed by anything will force you to search through the state and replace it (as some of the other answers have shown).
It's usually a better idea to simplify things to improve readability and comprehension
Recall that React state is additive, so calling setState with just a partial state change will merge it with the existing state. You will only get this benefit if you're keying your data in the state.

you can add name property to input, and get target.name like this:
_handleInput(event) {
let name = event.target.name;
let value = event.target.value;
this.setState({[name] : value});
}
<input
onChange={this._handleInput}
value={this.state.key1}
name="key1"
/>

Related

How to modify input value correctly and modify array value at the same time?

I render the values that I have inside an object and an input next to each of them with that object index item as value. When I try to use the onChange prop, I cannot modify neither the rendered value nor the input value. How can I do this changing the state of the element in the array?
I want the Toto value to be modified as I change the input value
My object players looks like this:
[
{
"name": "Toto";
},
{
"name": "Lolz",
}
]
Here, I try to render my table:
modifyItem = (event, index) => {
this.state.players[index].name = event.target.value
//my problem is clearly here
}
render() {
const playersList = [...new Array(this.state.players.length)].map((it, index) => {
return (
<tr key={index}>
<td>{this.state.players[index].name}</td>
<input type="text" value={this.state.players[index].name} onChange={this.modifyItem}/>
</td>
</tr>
)
})
return () {
<div>
{playersList}
</div>
}
What I want to do is:
In each element of the table AND input ( for example the first one Toto), I want to modify this value in the input and consecutively in the table. I can't seem to find the correct answer.
I've created a codesandbox example for you here:
https://codesandbox.io/s/vnkoxop6z3
You need to store your values in state, and when you modify each item you need to know what the new value is via the onChange callback, and the index of that current value.
From here you can duplicate the array by spreading it into a new one, access that particular index item on the new array, and update the name with the new value.
Once you have done this. setState with the new array.
class Component extends React.Component {
constructor() {
super();
this.state = {
values: [
{
name: "Toto"
},
{
name: "Lolz"
}
]
};
}
modifyItem(e, index) {
const newValue = e.target.value;
const values = [...this.state.values];
values[index].name = newValue;
this.setState({
values
});
}
render() {
return this.state.values.map((value, index) => {
return (
<Fragment>
<label>{value.name}</label>
<input
type="text"
value={value.name}
onChange={e => this.modifyItem(e, index)}
/>
</Fragment>
);
});
}
}

setState update array value using index in react

I want to update array value using index, is below code ok?
handleChange = index => e => {
const { rocket } = this.state // ['tesla', 'apple', 'google']
rocket[index] = e.target.value
this.setState({ rocket })
}
my jsx
<div>{rocket.map((val,i) => <input type="text" onChange={handleChange(i)} value={val} />)}</div>
I know it worked, but just to be sure it's ok to mutate the state like that.
It's not okay to mutate state this way.
The following line mutates the array in the current state in a way that can lead to bugs in your program particularly with components down the Component tree using that state.
This is because the state is still the same array.
rocket[index] = e.target.value
//console.log(this.state.rocket) and you see that state is updated in place
Always treat state as immutable
You can remedy this by writing.
const newRocket = [
...rocket.slice(0, index),
e.target.value,
...rocket.slice(index + 1)
]
This way a new array is created and components in the Component tree can be updated when React does a reconciliation.
Note that
The only way to mutate state should be through calls to Component.setState.
Now that you have a new array, you can update the component state like so:
this.setState({ rocket: newRocket })
Instead of changing existing value, you could use Array.prototype.splice().
The splice() method changes the contents of an array by removing existing elements and/or adding new elements.
var arr= ['A','B','E','D'];
arr.splice(2,1,'C')
console.log(arr)//The result will be ['A','B','C','D'];
.as-console-wrapper {max-height: 100% !important;top: 0;}
Stackblitz demo
CODE SNIPPET
class App extends Component {
constructor() {
super();
this.state = {
name: 'Demo using Array.prototype.slice()',
rocket: ['tesla', 'apple', 'google'],
link: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice'
};
}
handleChange(index, e) {
const { rocket } = this.state;
rocket.splice(index, 1, e.target.value)
this.setState({ rocket: [...rocket] }, () => {
//call back function of set state you could check here updated state
console.log(this.state.rocket)
});
}
render() {
return (
<div>
<b><a target="_blank" href={this.state.link}>{this.state.name}</a></b>
{
this.state.rocket.map((val, i) =>
<p key={i}>
<input type="text" onChange={(e) => { this.handleChange(i, e) }} value={val} />
</p>)
}</div>
);
}
}
render(<App />, document.getElementById('root'));

Changing state but then clearing input React

I'm writing my first React app and really struggling to do something quite basic.
I have an Input component that has an array in state, which, when it has two numbers, it sends them and a unique ID as an object up to a parent Component which stores the object in an array.
This is all fine and I can do it. The problem is clearing the inputs afterwards.
So far as I understand it, I need the value of the inputs to be stored in the Component state (in the array) when I do an on Change. Those values are then used for submitting the form.
However, if the inputs are getting their value from state, they need to have a value on render, which I don't want. I only want them to have a value after I've entered something into the input. I've tried using setState to replace the inputTable with an empty array after submission, but that's still not changing the values.
Here's the code - to reiterate, I want to find a way to just clear the inputs after I've submitted the array. At the moment it keeps saying that I'm changing an uncontrolled component into a controlled one, which I understand, but I don't understand how else I'm meant
Please trust that I've tried to solve this by myself, checking out MDN docs about forms and inputs, but I'm really not getting anywhere. I'd really appreciate the help.
import React, { Component } from 'react';
class Input extends Component {
constructor(props) {
super(props)
this.state = {
inputTable: [],
uniqueId: 1
}
this.handleChange = this.handleChange.bind(this);
this.sendTables = this.sendTables.bind(this);
}
async handleChange(e) {
e.preventDefault();
await this.setState({
inputTable: [
...this.state.inputTable, e.target.value
]
})
console.log(this.state.inputTable)
// how do I handle this onChange correctly?
}
async sendTables(e) {
e.preventDefault();
this.props.submitTable(this.state.inputTable, this.state.uniqueId);
let newArray = [];
await this.setState({
inputTable: newArray,
uniqueId: this.state.uniqueId + 1
})
console.log(this.state.inputTable)
// how can I clear the inputs?
}
render() {
return (
<div className="Input">
<form action="" onSubmit={this.sendTables}>
<input required type="number" name="value0" placeholder="a number..." onChange={this.handleChange} value={this.state.inputTable[0]} />
<span>X</span>
<input required type="number" name="value1" placeholder="multiplied by..." onChange={this.handleChange} value={this.state.inputTable[1]}/>
<input type="submit" value="Add times table" />
</form>
</div>
);
}
}
export default Input;
Parent component:
import React, { Component } from 'react';
import Input from './Input/Input';
class InputTimesTables extends Component {
constructor() {
super();
this.state = {
tables: [],
noInputs: 1
}
this.pushToTables = this.pushToTables.bind(this);
}
addInput() {
// this will be used to add an additional input
// it will increment no. inputs
}
async pushToTables(arr, id) {
let newTimesTable = {
timesTable: arr,
uniqueId: id
}
await this.setState({
tables: [...this.state.tables, newTimesTable]
})
// console.log(`The ITT state looks like:`, this.state.tables);
//this will take the two numbers in the array from the Input
// and push them to the tables array
// it won't run unless there are two numbers in that array
console.log('The main state array now looks like this: ', this.state.tables)
}
// clearTables(id){
// console.log(`splicing array no ${id}`);
// let newArray = this.state.tables;
// newArray.splice(id, 1);
// this.setState({
// tables: newArray
// })
// console.log(this.state.tables);
// // console.log(`The ITT state looks like:`, this.state.tables);
// }
render() {
return (
<div>
<Input submitTable={this.pushToTables}/>
<h3>Currently being tested on:</h3>
<ul>
</ul>
</div>
);
}
}
export default InputTimesTables;
Many thanks.
I think I've got it - the issue is that a number input is not actually a number - it's a string. So I can just set the arrays to ["", ""] and then reset them, and there's no problem. I hope this helps someone else if they run into it.
Thanks if you had a look!

Updating nested state in objects through an input element in React

I am creating a form that allows orders to be updated. The input fields have to be populated with the current state of each object that I render and I'd like to be able to edit the input field. I have simplified my code to just one input field and believe that I'm able to do the majority of what I'm attempting using the following code --
class EditOrderForm extends React.Component {
...
handleChange(e, key) {
const order = this.props.orders[key];
const updatedOrder = {
...order,
[e.target.name]: e.target.value
}
this.props.updateOrder(key, updatedOrder);
}
renderEditOrderForm(key) {
const order = this.props.orders[key]
return (
<div key={key}>
<form >
<input type="text" name="name" value={order.data.name} placeholder="order name" onChange={(e) => this.handleChange(e, key)} />
...
</form>
</div>
)
}
render() {
return (
<div>
<h2>Edit Orders</h2>
{
Object.keys(this.props.orders).map(this.renderEditOrderForm)
}
</div>
)
}
}
*************Parent Component*************
class AppComponent extends React.Component {
import EditOrderForm from './EditOrderForm';
...
updateOrder(key, updatedOrder) {
const orders = [...this.state.orders]
orders[key] = updatedOrder;
this.setState({ orders: orders });
}
...
}
The state that's set at the parent component level is an array of objects and the data structure for the objects that I'm passing to renderEditOrderForm() has the structure --
{
data: Object,
meta: Object,
__proto__: Object
}
Where data: Object contains the key-value pairs that I'm trying to change, in this case the key name nested under data: Object (above) and I would like put it back into the array once updated/edited. I am slightly able to update the name of an order however when I try to update it (say, type an 'x') the object now has this structure --
{
data: Object,
meta: Object,
name: "John Smithx"
__proto__: Object
}
I can intuit that [e.target.name]: e.target.value is probably the culprit, however I'm completely at a loss as to how I'm supposed to access the nested key name in data: Object -- I have tried e.target.data.name, however that gives me undefined and have tried a variety of other combinations. Without using Redux (unfortunately don't have time to learn due to time constraints), does anyone know how I can access/target the key name in order to update nested in data: Object?
You need to change the field order.data.name but your code is only adding a new field to the order object. Replace
handleChange(e, key) {
const order = this.props.orders[key];
const updatedOrder = {
...order,
[e.target.name]: e.target.value
}
this.props.updateOrder(key, updatedOrder);
}
with
handleChange(e, key) {
const order = this.props.orders[key];
let updatedOrder = { ...order };
updatedOrder.data[e.target.name] = e.target.value;
// or if you have only 1 field called name, you can use updatedOrder.data.name = e.target.value;
this.props.updateOrder(key, updatedOrder);
}

Notify react components about value change

Suppose that I have a component class which is responsible to change any number entered into textbox to text:
class NumbersToText extends Component {
onChange(event) {
const { target } = event;
const { value } = target;
if (hasNumbers(value)) {
target.value = numbersToText(value);
// HERE I NEED TO NOTIFY ABOUT CHANGES
}
}
render() {
return (
<span onChange={this.onChange}>
{this.props.children}
</span>
);
}
}
Now the usage would look something like this:
<NumbersToText>
<input onChange={this.saveValue}
</NumbersToText>
Let's say that all works, and the value gets changed to text.
Now the problem is that after I change numbers to text and assign that value to input onChange handlers are not executed again, thus saveValue is not called with updated value.
How should this problem be approached in order to trigger onChange handlers with new value?
I don't know exactly what you mean by numbers to text so I'll just assume you want to modify the value before calling the onChange function in the input, and also reflect that value in the input.
First of all, what you're doing will never work on React, React reflects internal virtual objects into the DOM, meaning you shloud not modify the DOM directly and instead you should modify this internal representantion (via setState, props) to reflect this change into the DOM.
There's also two types of inputs on React, controlled and uncontrolled. I will assume you want to use this on uncontrolled inputs.
The only possible solution I can see, is to transform the input using the React.cloneElement function adding a aditional step before calling the input's onChange callback.
Here's a possible implementation that will make the input uppercase.
class UpperCase extends React.Component {
constructor(props) {
super(props);
}
onChange(e, input, next) {
let value = input.value || '';
value = value.toUpperCase();
input.value = value;
next(value);
}
render() {
let childs = React.Children.map(this.props.children, child => {
let input = null; //Will take advantage of javascript's closures
let onChangeChild = child.props.onChange.bind(child);
return React.cloneElement(child, {
ref: ref => input = ref,
onChange: e => {
this.onChange(e, input, onChangeChild)
}
});
});
return (
<span>
{childs}
</span>
);
}
}
And you can use it like this:
<UpperCase>
<input onChange={(val) => console.log(val)}></input>
<textarea onChange={(val) => console.log(val)}></textarea>
</UpperCase>
Thanks to #tiagohngl I came up with a similar, but maybe a little less cluttered (without cloning elements) way:
class NumbersToText extends Component {
onChange(event) {
const { target } = event;
const { value } = target;
if (hasNumbers(value)) {
target.value = numbersToText(value);
this.childrenOnChange(event);
}
}
childrenOnChange(event) {
const { children } = this.props;
React.Children.forEach(children, child => {
if (child.props.onChange) {
child.props.onChange(event);
}
});
}
render() {
return (
<span onChange={this.onChange}>
{this.props.children}
</span>
);
}
}
export default class NumbersToText extends React.Component {
constructor(props) {
super(props)
this.onChange = this.onChange.bind(this);
}
componentWillMount() {
this.setState({ anyData: [] });
}
onChange(event) {
this.setState({anyData: event.target.value},
()=>{console.log("AnyData: "+this.state.anyData)});
// callback to console.log after setState is done
}
render() {
return (
<input type="text"
value={this.state.anyData}
onChange={this.onChange} />
);
}
}
As you mention that,
onChange is not called after changed value.
There are multiple possibilities.
onChange is not binded.
There are no state change in render method, so it will not re-render
make use of console.log() to trace the problem
I slightly ammend the code for illustration.
Hope it helps.
How react handle State Change (answer I posted before)

Categories

Resources