Multiple state changes - reactjs

I am following a lesson. I have a controlled form and a handleInput method to reflect any changes in input. However, I didn't understand why he writes [name] instead of name inside of the handleInput method. A detailed explanation will be appreciated.
handleInput(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
[name]: value
})
}
Form Structure:
<Form>
<FormGroup>
<Label htmlFor="firstname" md={2}>First Name</Label>
<Input type="text" id="firstname" name="firstname"
value={this.state.firstname}
onChange={this.handleInput} />
</FormGroup>
<FormGroup>
<Label htmlFor="lastname" md={2}>Last Name</Label>
<Input type="text" id="lastname" name="lastname"
value={this.state.lastname}
onChange={this.handleInput} />
</FormGroup>
</Form>

This syntax is for computed property name
Its a new ES6 feature which computed the property name of the object.
Before this you had to specify the property directly on the object.
var data = {};
data[name] = value;
this.setState(data);
With the new syntax you can do it inline with the object definition.
this.setState({
[name]: value
})

In the form, the input elements has names. In handleInput function, when you do
const name = target.name;
This name gets assigned to name
Now, on doing
this.setState({
[name]: value
})
name gets evaluated to firstname or lastname.
You can read more about this feature here

It's an ES6 feature, the square brackets allows you to programmatically reference the property of the object.

Declaring the key in state as just name: value would reference an object key in state actually named name. Instead using [name] references the value of name attained from the input (const name = target.name;) which will in turn reference the corresponding key in the state.

handleInput(event) {
const target = event.target;
const value = target.value;
const name = target.name;
// let name now = "blocker" and value = "something"
this.setState({
[name]: value
})
}
on console.log(this.state)
output will be
{blocker:something}
Now if you remove [] from name
this.setState({
name: value
})
then on console.log(this.state)
output will be
{name:something}

Related

Array destructuring

handleChange = (event) => {
const { name, value } = event.target;
this.setState({ [name]: value });
};
Why need to use array destructuring inside setState? Since i already use {} destructuring name and value why I cannot just write this.setState({ name: value }) ?
As mentioned earlier, this method is used to handle multiple inputs. Say you have two input elements in your code:
<input name="email" onChange={handleChange} />
<input name="password" onChange={handleChange} />
When you do
this.setState({ [name]: value });
you can use a single onChange handler to update the state for both, email as well as password. This would basically translate to:
this.setState({ email: 'xyz' });
this.setState({ password: 'abc' });
If you use name instead of [name], that would basically update the name key in the state, not the input field name i.e. email & password in this case.

I am trying to set a condition on the value of an input component in react (MERN)

I am working on an Instagram clone in MERN to practice and learn.
The registration form has input, and you can register either with an e-mail or a mobile number. I am trying to make a ternary operator in the value of the input component, but I get errors when I run the code.
Validate Email function:
const validateEmail = (userOption) => {
let checker = /\S+#\S+\.\S+/;
return checker.test(userOption);
}
Input component
<label htmlFor="email" />
<input type="text"
name="email"
value= { validateEmail(registerOption) ? {user.email} : {user.mobile} }
onChange={onChangeHandler}
placeHolder="Mobile Number or Email" />
It does not recognize user.email or user.mobile, even though when I have it without the ternary it does. (only for email or only for mobile works)
I am also not sure if I have to change htmlFor as well.
Thank you!
As explained by the other answers, you don't need to use {} to wrap user.email or user.mobile again. However, your onChangeHandler is only going to update the email field and not the mobile field since you are updating based on event.target.name.
I would suggest, you validate the input on click of a button instead on switching the value of <input /> on every user input.
Add a state (let's say userInput) and update that in the onChangeHandler.
const [userInput, setUserInput] = useState('');
const onChangeHandler = (e) => {
setUserInput(e.target.value);
....
When the Sign up / Sign In button is clicked, we would validate the userInput and proceed accordingly.
const onSubmit = () => {
const isEmail = validateEmail(userInput);
if (isEmail) setUser({...user, email: userInput});
else setUser({...user, mobile: userInput});
// proceed to signin / signup or show an error message
}
Your Input will accordingly change to
<input
type="text"
value={userInput}
onChange={onChangeHandler}
placeHolder="Mobile Number or Email"
/>
Alternatively
If you want to update the user.email or user.mobile on every user input, you can use the following onChangeHandler method -
const onChangeHandler = (e) => {
const userInput = e.target.value;
const isEmail = validateEmail(userInput);
if (isEmail) setUser({ ...user, email: userInput, mobile: '' });
else setUser({ ...user, mobile: userInput, email: '' });
}
Your Input will accordingly change to
<input
type="text"
value={user.email || user.mobile}
onChange={onChangeHandler}
placeHolder="Mobile Number or Email"
/>
Two things here:-
Remove the { } around user.email and user.mobile.
Don't depend on name attribute of input field for your scenario. Let it be something like "contact" unrelated to your js code. Also directly use user.email as parameter to validationEmail function.
const onChangeHandler = (e) => {
const {value} = e.target;
const isValidEmail = validateEmail(value);
const user = isValidEmail?{...user,email:value}:{...user,mobile:value}
setUser(user);
}
Validate Email function:
const validateEmail = (userOption) => {
let checker = /\S+#\S+\.\S+/;
return checker.test(userOption);
}
Input component
<label htmlFor="contact" />
<input type="text"
name="contact"
value= {validateEmail(user.email)?user.email:user.mobile}
onChange={onChangeHandler}
placeHolder="Mobile Number or Email" />
Behaviour:- When the email is valid, the input box will show that as the value. As soon as it gets invalid the input box will show mobile number as the value. See if this is what you intended!
The {} is a special syntax which is used to evaluate JS expression.
So when you write value= { validateEmail(registerOption) ? {user.email} : {user.mobile} } , you don't need to wrap user.email and user.mobile in {} again, as they will be treated as JS value since they are already wrapped inside curly brackets.
The value prop should look like so:
value={ validateEmail(registerOption) ? user.email : user.mobile }
Using the statement {user.email} will be parsed as
{ // scope
user.email // expression
}

Change value on dynamic input fields in reactjs

I am trying to change the input value on dynamically added input fields.
Each input field value is set to a state value which is made of an array.
Seems like there should be a simple solution for this.. But I can't just figure it out.
JSfiddle:
https://jsfiddle.net/o51Lkvm6/1/
handleInputChange = (e) => {
this.setState({
[e.target.name]: e.target.value
});
}
render() {
return (
<div>
{ this.state.data.map((d, index) =>
<input name={d.Name} type="text" className="form-control"
value={d.Name} onChange={this.handleInputChange} />
)}
</div>
);
}
Update:
Is it possible to solve this without having to use defaultvalue? Since React does not recommend "Uncontrolled Components"?
First of all there are couple issues with your code:
You forgot to bind your handler method or use arrow function to
preserve this context of a class. To fix that you can either put this
in Test constructor:
this.handleInputChange = this.handleInputChange.bind(this)
or modify your existing function to:
handleInputChange = e => {};
Input value should actually use the value which
corresponds to current item from state, like that:
value={this.state.data[index]["Name"]}
Later to access proper item in your stateData you have to somehow
store that index in the input. I did this by assigning it to
data-index attribute. Also you forgot to include key prop:
<input
key={d.ID}
data-index={index}
name={d.Name}
type="text"
className="form-control"
value={this.state.data[index]["Name"]}
onChange={this.handleInputChange}
/>
In your actual handleInputChange you were not targeting the correct
thing. You need to first get the appropriate item from the array and
then modify the name. I did it by copying the actual state and later
assigning it:
handleInputChange = e => {
const stateDataCopy = this.state.data.slice();
const objectCopy = Object.assign({}, stateDataCopy[e.target.dataset.index]);
objectCopy["Name"] = e.target.value;
stateDataCopy[e.target.dataset.index] = objectCopy;
this.setState({ data: stateDataCopy });
};
Here you can find working example:
ok I fixed it for you
do these 2 things
handleInputChange(e){ make this an arrow function so it has the concept of this like so: handleInputChange = (e) => {
and use defaultValue instead of value in the input
updated fiddle for you: https://jsfiddle.net/a17gywvp/1/

Initializing controlled React input with blank value and type="number"

Problem:
I have a controlled input, using Material-UI TextField. I want to have purely numbers, and support type="number" for mobile keyboards, but when having empty string as default, and having a parseInt in the onChange, it will not allow 0 (zero). All other numbers are displayed correctly.
It even correctly sets 0 in the underlying object bound to the TextField.
In other inputs i have default values as empty strings (heavily simplified), like shown:
<TextField
name={"inputToBeSaved"}
value={this.state.objectForSaving.inputToBeSaved|| ''}
onChange={this.handleChange}
variant="outlined"
/>
With corresponding handleChange:
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState(prevState => ({
objectForSaving: {
...prevState.objectForSaving,
[name]: value
}
}))
}
The example above works as espected, but not when trying to work with numbers:
<TextField
type="number"
name={"numberToBeSaved"}
value={this.state.objectForSaving.numberToBeSaved|| ''}
onChange={this.handleChange}
variant="outlined"
/>
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.type === 'number' ? parseInt(target.value) : target.value;
const name = target.name;
this.setState(prevState => ({
objectForSaving: {
...prevState.objectForSaving,
[name]: value
}
}))
}
I know that defaulting to 0, or making it uncontrolled will fix it, but some times I do have values there from the server, so I want to reuse the components.
I have tried to look at this post, but did not find any good solution to keep it controlled: Initializing React number input control with blank value?
0 is falsey, so in the following condition:
this.state.objectForSaving.numberToBeSaved|| ''
if numberToBeSaved is 0, then the expression will evaluate to empty string.
If you are just trying to check if numberToBeSaved is defined, then you can do this more robustly with:
this.state.objectForSaving.numberToBeSaved === undefined ? '' : this.state.objectForSaving.numberToBeSaved
You can convert the value to string when setting the TextField prop.
const {
objectForSaving: {
inputToBeSaved = '',
},
} = this.state
<TextField
name={"inputToBeSaved"}
value={String(inputToBeSaved)}
onChange={this.handleChange}
variant="outlined"
/>

Do not mutate state directly, Use setState() react/no-direct-mutation-state in React JS

<input
defaultValue={this.props.str.name}
ref={(input) => { this.state.name = input; }}
name="name"
type="text"
className="form-control"
onChange={this.handleInputChange}
/>
handleInputChange(event) {
this.setState({
[event.target.name]: event.target.value
});
}
if(this.state.name.value === "") {
this.msg.show('Required fields can not be empty', {
time: 2000,
type: 'info',
icon: <img src="img/avatars/info.png" role="presentation"/>
});
}
I'm trying to set the default value like that and wanted to access it as well. I did like this and accessed the value with this.state.name.value but the thing is its working but showing the warning as
Do not mutate state directly, Use setState()
react/no-direct-mutation-state .
Getting "Do not mutate state directly, Use setState()", Why?
Because, you are mutating the state value inside ref callback method to store the node ref, Here:
this.state.name = input;
Solution:
Don't use state variable to store the reference, You can directly store
them in component instance because that will not change with time.
As per DOC:
The state contains data specific to this component that may change
over time. The state is user-defined, and it should be a plain
JavaScript object.
If you don’t use it in render(), it shouldn’t be in the state. For
example, you can put timer IDs directly on the instance.
Since you are using controlled input element, ref is not required. Directly use this.state.name with input element value property and this.state.name to access the value.
Use this:
<input
value={this.state.name || ''}
name="name"
type="text"
className="form-control"
onChange={this.handleInputChange}
/>
If you wanted to use ref then store the ref directly on instance, remove value property and you can remove the onChange event also, Use it like this:
<input
ref={el => this.el = el}
defaultValue={this.props.str.name}
name="name"
type="text"
className="form-control"
/>
Now use this ref to access the value like this:
this.el.value
you can instead clone the entire property value inside the with spread operator and then reform or edit the value for example :
state = {Counters: [{id:1,value:1},{id: 2,value: 2},{id: 3,value: 3},{id: 4,value: 4}]}
increment = (Counter) => {
//This is where the state property value is cloned
const Counters = [...this.state.Counters];
console.log(Counters);
const index = Counters.indexOf(Counter)
Counters[index].value++
this.setState({
Counters: this.state.Counters
})
}
Change your line number 3 as
ref={(input) => { this.setState({name: input}); }}

Resources