How to preserve initial state on change of input value in React? - javascript

I have the following code, simply want to update the text value on input field change.
import React, { useState } from 'react';
const Form = () => {
const initialState = {
name: 'John',
age: 25
};
const [{ name, age }, setFormState] = useState(initialState);
const handleNameChange = (e) => {
setFormState({
name: e.target.value
});
};
const handleAgeChange = (e) => {
setFormState({
age: e.target.value
})
};
return (
<>
<form onSubmit={(e) => e.preventDefault()}>
<label htmlFor='name'>Name: </label>
<input type='text' id='name' name='name' placeholder={name} onChange={handleNameChange} />
<p>The person's name is {name}.</p>
<br />
<label htmlFor='age'>Age: </label>
<input type='text' id='age' name='age' placeholder={age} onChange={handleAgeChange} />
<p>His/her age is {age}.</p>
</form>
</>
)
}
export default Form;
A working example here: https://codesandbox.io/s/react-playground-forked-tskj9?file=/Form.js
Problem is, when input a value in the name field, age field is cleared, vice versa. I understand that this might be due to the initialState is no longer valid after change in the field, but how can I preserve the values in one <input> and <p> while input in another?

While you could fix it by including the other property in setFormState, eg:
setFormState({
name: e.target.value
age,
});
Functional components are not class components - feel free to separate values out into separate variables if they're not related. This'll make the syntax a lot easier:
const Form = () => {
const [name, setName] = React.useState('John');
const [age, setAge] = React.useState(25);
const handleNameChange = (e) => {
setName(e.target.value);
};
const handleAgeChange = (e) => {
setAge(e.target.value);
};
return (
<form onSubmit={(e) => e.preventDefault()}>
<label htmlFor='name'>Name: </label>
<input type='text' id='name' name='name' placeholder={name} onChange={handleNameChange} />
<p>The person's name is {name}.</p>
<br />
<label htmlFor='age'>Age: </label>
<input type='text' id='age' name='age' placeholder={age} onChange={handleAgeChange} />
<p>His/her age is {age}.</p>
</form>
)
}
ReactDOM.render(<Form />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class="react"></div>

In useState hook the properties are not merged as it used to be in setState function within class components. You will have to include the missing fields, to keep them in the object.
setFormState({
name: e.target.value,
age,
});

Related

How can I update the state of an object nested in an array from a child component?

I have RecipeCreate component, and inside of that I want a user to be able to render as many IngredientDetailsInput components as needed to complete the recipe.
I do this by creating an empty object in an ingredients array within RecipeCreate, and then iterate over this array of empty objects, generating a corresponding IngredientDetailsInput for each empty object.
From within IngredientDetailsInput I want to update the empty corresponding empty object in RecipeCreate with data passed up from IngredientDetailsInput. Since IngredientDetailsInput has the index of where it's object lives in the ingredients array in it's parent component, I believe this is possible.
Here is working sandbox that demonstrates the issue
I'm close, but each time the handleChange runs it is creating a new object in the ingredients array and I'm not sure why, or what other options to use besides handleChange - I'd like there not to have to be a form submit if possiblee
And here is code for both components
import React, { useState } from "react";
const RecipeCreate = (props) => {
const [ingredients, setIngredients] = useState([]);
const [recipeTitle, setRecipeTitle] = useState("");
//if an ingredient object has been added to the ingredients array
//render an IngredientDetailsInput component, passing along the index position
//so we can update it later
const renderIngredientComponents = () => {
if (ingredients) {
return ingredients.map((_, index) => {
return (
<IngredientDetailsInput
key={index}
position={index}
updateIngredientArray={updateIngredientArray}
/>
);
});
}
};
//broken function that should find the object position in ingredients
//and copy it, and non-mutated ingredient objects to a new object, and set the state to this
//new object
const updateIngredientArray = (key, value, position) => {
return setIngredients((prevIngredients) => {
console.log(ingredients)
return [...prevIngredients, prevIngredients[position][key] = value]
});
};
//allows the user to add another "ingredient", rendering a new IngredientDetailsInput component
//does so by adding a new, empty object to the ingredients array
const addElementToArray = () => {
setIngredients((prevIngredients) => [...prevIngredients, {}]);
};
return (
<div>
<div>
<form>
<div>
<label>Recipe Title</label>
<input
type="text"
name="recipeTitle"
value={recipeTitle}
onChange={(e) => setRecipeTitle(e.target.value)}
/>
</div>
<div>
<p>Ingredients</p>
{renderIngredientComponents()}
<div>
<p onClick={() => addElementToArray()}>+ ingredient</p>
</div>
</div>
<div></div>
<button type="submit">Submit</button>
</form>
</div>
</div>
);
};
export default RecipeCreate;
//child component that should allow changes to bubble up to RecipeCreate
export function IngredientDetailsInput(props) {
return (
<div>
<input
type="number"
name="measurement"
id="measurement"
placeholder="1.25"
onChange={(e) =>
props.updateIngredientArray(
"measurement",
e.target.value,
props.position
)
}
/>
<div>
<label htmlFor="measurementType">type</label>
<select
id="unitType"
name="unitType"
onChange={(e) =>
props.updateIngredientArray(
"unitType",
e.target.value,
props.position
)
}
>
<option>tbsp</option>
<option>cup</option>
<option>tspn</option>
<option>pinch</option>
<option>ml</option>
<option>g</option>
<option>whole</option>
</select>
</div>
<input
type="text"
name="ingredientName"
id="ingredientName"
placeholder="ingredient name"
onChange={(e) =>
props.updateIngredientArray(
"ingredientName",
e.target.value,
props.position
)
}
/>
</div>
);
}
The assignment prevIngredients[position][key] = value returns value instead of prevIngredients[position][key]. Thus when you setting the state, it returns the previous stored ingredients as well as that value.
const updateIngredientArray = (key, value, position) => {
return setIngredients((prevIngredients) => {
console.log(ingredients)
return [...prevIngredients, prevIngredients[position][key] = value]
});
};
A quick fix would be to recopy a new array of the current ingredient, then changing the position and key that you want.
const updateIngredientArray = (key, value, position) => {
const tmp = ingredients.map((l) => Object.assign({}, l));
tmp[position][key] = value;
setIngredients(tmp);
};
May be you can try like this?
const {useState} = React;
const App = () => {
const [state, setState] = useState([
{
name: "",
amount: "",
type: ""
}
]);
const addMore = () => {
setState([
...state,
{
name: "",
amount: "",
type: ""
}
]);
};
return (
<div className="App">
<h1>Recipe</h1>
<h2>Start editing to see some magic happen!</h2>
<label>Recipe Title</label>
<input type="text" />
<br /> <br />
<div onClick={addMore}>Add More +</div>
{state && state.map((val, ikey) =>
<div>
<br />
<label>Ingredients</label>
<input type="text" placeholder="Name" />
<input type="text" placeholder="Amount" />
<select>
<option>tbsp</option>
<option>cup</option>
<option>tspn</option>
<option>pinch</option>
<option>ml</option>
<option>g</option>
<option>whole</option>
</select>
</div>
)}
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

What is the proper way to clear a React Form?

I have a form that has a few different type of elements (textbox, checkbox, etc.) that was created using Reactjs. I have been having a hard time trying to figure out how to clear a form. I've googled and not really sure of those solutions. I tried something like the following but found it was of no use.
What I want to happen is if the user fills out the form and decides to clear the form, once they click 'Clear Form', the form should reset. All the fields need to be blank again.
handleClearForm(){
this.setState({
decisionDate: '',
Veggies:'',
fullName:'',
comment:''
})
}
How can I clear a form in react? Any help would be much appreciated.
Code
Check this improved code
class App extends React.Component{
constructor(){
super()
this.state = {
decisionDate: '',
Veggies:'',
fullName:'',
comment:''
}
}
setdecisionDateValue (value) {
this.setState({
decisionDate: value
})
}
componentDidMount(){
$( "#decisionDate" ).datepicker({
onSelect: this.setdecisionDateValue
});
}
handleClearForm = () => {
this.setState({
decisionDate: '',
Veggies:'',
fullName:'',
comment:''
})
}
handleChange = (e) => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({[name]: value})
}
render(){
const { decisionDate,Veggies,fullName,comment} = this.state;
return(
<div>
<input type="text" name="fullName"
placeholder="Full Name"
onChange={this.handleChange}
value={fullName}/><br /><br />
<input type="text"
name="decisionDate"
id="decisionDate"
onChange={this.handleChange}
placeholder="MM/DD/YYYY"
value={this.state.decisionDate} /><br /><br />
<textarea name="comment"
value={comment}
onChange={this.handleChange}/><br /><br />
<input
type="checkbox"
name="Veggies"
onChange={this.handleChange}
checked={Veggies}
/>Veggies<br /><br />
<button onClick={this.handleClearForm}>
Clear Form
</button>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById("root")
)

Unable to type into React input field

I am unable to type any input into my input field. I am using React, and have already set a handleChange and a handleSubmit function. The first two input fields, for 'name' and 'email', take input just fine. But for 'favoriteCity', it doesn't seem to work.
I am wondering if it is due to a MongoDB error that I am getting.
class UserPage extends Component {
state = {
user: [],
newUser: {
name: '',
email: '',
favoriteCity: ''
}
}
getAllUsers = () => {
axios.get('/api/users')
.then(res => {
this.setState({ user: res.data })
})
}
componentDidMount() {
this.getAllUsers()
}
handleChange = event => {
const newUser = { ...this.state.newUser };
newUser[event.target.name] = event.target.value;
this.setState({ newUser: newUser});
}
handleSubmit = event => {
event.preventDefault()
axios.post('/api/users', this.state.newUser)
.then(res => {
this.props.history.push(`/users/${res.data._id}`)
})
}
render() {
return (
<div>
{ /* This shows a list of All Users */ }
{this.state.user.map(user => (
<div key={user._id}>
<Link to={`/users/${user._id}`}>{user.name}</Link>
</div>
))}
<h1>New User Page</h1>
<form onSubmit={this.handleSubmit}>
<label>Name: </label>
<input
type="text"
name="name"
placeholder="Name?"
value={this.state.newUser.name}
onChange={this.handleChange}
/>
<label>Email: </label>
<input
type="text"
name="email"
placeholder="Email?"
value={this.state.newUser.email}
onChange={this.handleChange}
/>
<label>Favorite City: </label>
<input
type="text"
name="city"
placeholder="Favorite City?"
value={this.state.newUser.favoriteCity}
onChange={this.handleChange}
/>
<Button
type="submit"
value="Submit"
variant="contained"
color="primary"
>
Create User
</Button>
</form>
</div>
);
}
}
export default UserPage;
Please help.
Weird that email works fine, from what you posted your handleChange function is only updating the name on the newUser.
What you should see is what you type in all the inputs appear in the name input.
To fix this, you should probably have separate change handlers for each input:
handleNameChange
handleEmailChange
...
You should also consider storing name, email etc.. at the root of your state instead of nesting them in an object, that'll simplify the handler functions code.

Manage multiple form inputs and it's value via React state

Let me start with an example so you guys can know what is the issue and what I want to achieve.
In my project I'll have multiple user form on the same page and number of forms will be dynamic (depends on user number if there are 3 users then 3 forms, if 10 users then 10 forms etc).
Let's assume all forms will have 3 fields (keep it simple here) like firstName , middleName , lastName.
Now let assume we have 3 users so 3 inputs will appear on the page.
<form>
<input type="text" name="firstName" value="" />
<input type="text" name="middleName" value="" />
<input type="text" name="lastName" value="" />
</form>
We have 3 users this time so above form will appear 3 times. Here actually what I have taken only one form for all 3 users. What I have done is shown below.
<form>
for (let i = 1; i <= 3; i++) {
<input type="text" name="firstName" value="" />
<input type="text" name="middleName" value="" />
<input type="text" name="lastName" value="" />
}
<input type="submit" value="Submit">Apply</button>
</form>
When user submits the form I want an array of value for each form field.
What and How result I want is below.
['tome hanks' , 'shahrukh khan', 'john'] // firstname of all 3 users
['tome hanks' , 'shahrukh khan', 'john'] // middlename of all 3 users
['tome hanks' , 'shahrukh khan', 'john'] // lastname of all 3 users
I have tried this tutorial but not exactly what I need.
Maybe I can achieve this using React state but don't know how?
If Redux is helpful than it's fine for me.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
users: [
{ firstName: 'John1', middleName: 'Daniel1', lastName: 'Paul1' },
{ firstName: 'John2', middleName: 'Daniel2', lastName: 'Paul2' },
{ firstName: 'John3', middleName: 'Daniel3', lastName: 'Paul3' },
{ firstName: 'John4', middleName: 'Daniel4', lastName: 'Paul4' },
],
};
}
_onChangeUser = (index, field, event) => {
const newValue = event.target.value;
this.setState(state => {
const users = [
...state.users.slice(0, index),
{
...state.users[index],
[field]: newValue,
},
...state.users.slice(index + 1),
];
return {
users,
};
});
};
_onSubmit = event => {
event.preventDefault();
// Do something with this.state.users.
console.log(this.state.users);
};
render() {
return (
<div className="App">
<form onSubmit={this._onSubmit}>
{this.state.users.map((user, index) => (
<div key={index}>
<input
value={user.firstName}
onChange={this._onChangeUser.bind(this, index, 'firstName')}
/>
<input
value={user.middleName}
onChange={this._onChangeUser.bind(this, index, 'middleName')}
/>
<input
value={user.lastName}
onChange={this._onChangeUser.bind(this, index, 'lastName')}
/>
</div>
))}
<button type="submit">Submit</button>
</form>
</div>
);
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"><div>
Ok. Declare your state variable as an array. Below should fulfill your requirement.
constructor(props){
super(props)
this.state={
firstNameArray:[],
middleNameArray:[],
lastNameArray:[],
fName:” “,
mName:””,
lName:””
}
this.changeFirstName = this.changeFirstName.bind(this);
this.changeMiddleName= this.changeMiddleName.bind(this);
this.changeLastName=this.changeLastName.bind(this);
}
changeFirstName(event){
this.setState({
firstNameArray:event.target.value,
fName: event.target.value
})
changeMiddleName(event){
this.setState({
middleNameArray: event.target.value,
mName: event.target.value
})
}
changeLastName(event){
this.setState({
lastNameArray: event.target.value,
lName: event.target.value
})
Call each function on your input field like below
<input type=‘text’ name=‘fName’ value= {this.state.fName} onChange={this.changeFirstName} />
Do it in the same way for other two input fields as well. Hope this answers your question.

How do I edit multiple input controlled components in React?

I have a component that stores a contact object as state - {firstName: "John", lastName: "Doe", phone: "1234567890} I want to create a form to edit this object but if I want the inputs to hold the value of the original contact parameter, I need to make each input a controlled component. However, I don't know how to create a handleChange function that will adjust to each parameter because my state only holds {contact: {...}}. Below is what I currently have -
getInitialState: function () {
return ({contact: {}});
},
handleChange: function (event) {
this.setState({contact: event.target.value });
},
render: function () {
return (
<div>
<input type="text" onChange={this.handleChange} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChange} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChange} value={this.state.contact.lastName}/>
</div>
);
}
I wish in my handleChange I can do something like
handleChange: function (event) {
this.setState({contact.firstName: event.target.value });
}
There's a "simple" way to do this, and a "smart" way. If you ask me, doing things the smart way is not always the best, because I may be harder to work with later. In this case, both are quite understandable.
Side note: One thing I'd ask you to think about, is do you need to update the contact object, or could you just keep firstName etc. directly on state? Maybe you have a lot of data in the state of the component? If that is the case, it's probably a good idea to separate it into smaller components with narrower responsibilities.
The "simple" way
changeFirstName: function (event) {
const contact = this.state.contact;
contact.firstName = event.target.value;
this.setState({ contact: contact });
},
changeLastName: function (event) {
const contact = this.state.contact;
contact.lastName = event.target.value;
this.setState({ contact: contact });
},
changePhone: function (event) {
const contact = this.state.contact;
contact.phone = event.target.value;
this.setState({ contact: contact });
},
render: function () {
return (
<div>
<input type="text" onChange={this.changeFirstName.bind(this)} value={this.state.contact.firstName}/>
<input type="text" onChange={this.changeLastName.bind(this)} value={this.state.contact.lastName}/>
<input type="text" onChange={this.changePhone.bind(this)} value={this.state.contact.phone}/>
</div>
);
}
The "smart" way
handleChange: function (propertyName, event) {
const contact = this.state.contact;
contact[propertyName] = event.target.value;
this.setState({ contact: contact });
},
render: function () {
return (
<div>
<input type="text" onChange={this.handleChange.bind(this, 'firstName')} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChange.bind(this, 'lastName')} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChange.bind(this, 'phone')} value={this.state.contact.lastName}/>
</div>
);
}
Update: Same examples using ES2015+
This section contains the same examples as shown above, but using features from ES2015+.
To support the following features across browsers you need to transpile your code with Babel using e.g.
the presets es2015 and react,
and the plugin stage-0.
Below are updated examples, using object destructuring to get the contact from the state,
spread operator to
create an updated contact object instead of mutating the existing one,
creating components as Classes by
extending React.Component,
and using arrow funtions to
create callbacks so we don't have to bind(this).
The "simple" way, ES2015+
class ContactEdit extends React.Component {
changeFirstName = (event) => {
const { contact } = this.state;
const newContact = {
...contact,
firstName: event.target.value
};
this.setState({ contact: newContact });
}
changeLastName = (event) => {
const { contact } = this.state;
const newContact = {
...contact,
lastName: event.target.value
};
this.setState({ contact: newContact });
}
changePhone = (event) => {
const { contact } = this.state;
const newContact = {
...contact,
phone: event.target.value
};
this.setState({ contact: newContact });
}
render() {
return (
<div>
<input type="text" onChange={this.changeFirstName} value={this.state.contact.firstName}/>
<input type="text" onChange={this.changeLastName} value={this.state.contact.lastName}/>
<input type="text" onChange={this.changePhone} value={this.state.contact.phone}/>
</div>
);
}
}
The "smart" way, ES2015+
Note that handleChangeFor is a curried function:
Calling it with a propertyName creates a callback function which, when called, updates [propertyName] of the
(new) contact object in the state.
class ContactEdit extends React.Component {
handleChangeFor = (propertyName) => (event) => {
const { contact } = this.state;
const newContact = {
...contact,
[propertyName]: event.target.value
};
this.setState({ contact: newContact });
}
render() {
return (
<div>
<input type="text" onChange={this.handleChangeFor('firstName')} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChangeFor('lastName')} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChangeFor('phone')} value={this.state.contact.lastName}/>
</div>
);
}
}
ES6 one liner approach
<input type="text"
value={this.state.username}
onChange={(e) => this.setState({ username: e.target.value })}
id="username"/>
The neatest approach
Here is an approach that I used in my simple application. This is the recommended approach in React and it is really neat and clean. It is very close to ArneHugo's answer and I thank hm too. The idea is a mix of that and react forms site.
We can use name attribute of each form input to get the specific propertyName and update the state based on that. This is my code in ES6 for the above example:
class ContactEdit extends React.Component {
handleChangeFor = (event) => {
const name = event.target.name;
const value = event.target.value;
const { contact } = this.state;
const newContact = {
...contact,
[name]: value
};
this.setState({ contact: newContact });
}
render() {
return (
<div>
<input type="text" name="firstName" onChange={this.handleChangeFor} />
<input type="text" name="lastName" onChange={this.handleChangeFor}/>
<input type="text" name="phone" onChange={this.handleChangeFor}/>
</div>
);
}
}
The differences:
We don't need to assign state as value attribute. No value is needed
The onChange method does not need to have any argument inside the function call as we use name attribute instead
We declare name and value of each input in the begening and use them to set the state properly in the code and we use rackets for name as it is an attribute.
We have less code here and vey smart way to get any kind input from the form because the name attribute will have a unique value for each input.
See a working example I have in CodPen for my experimental blog application in its early stage.
There are two ways to update the state of a nested object:
Use JSON.parse(JSON.stringify(object)) to create a copy of the object, then update the copy and pass it to setState.
Use the immutability helpers in react-addons, which is the recommended way.
You can see how it works in this JS Fiddle. The code is also below:
var Component = React.createClass({
getInitialState: function () {
return ({contact: {firstName: "first", lastName: "last", phone: "1244125"}});
},
handleChange: function (key,event) {
console.log(key,event.target.value);
//way 1
//var updatedContact = JSON.parse(JSON.stringify(this.state.contact));
//updatedContact[key] = event.target.value;
//way 2 (Recommended)
var updatedContact = React.addons.update(this.state.contact, {
[key] : {$set: event.target.value}
});
this.setState({contact: updatedContact});
},
render: function () {
return (
<div>
<input type="text" onChange={this.handleChange.bind(this,"firstName")} value={this.state.contact.firstName}/>
<input type="text" onChange={this.handleChange.bind(this,"lastName")} value={this.state.contact.lastName}/>
<input type="text" onChange={this.handleChange.bind(this,"phone")} value={this.state.contact.phone}/>
</div>
);
}
});
ReactDOM.render(
<Component />,
document.getElementById('container')
);
Here is generic one;
handleChange = (input) => (event) => {
this.setState({
...this.state,
[input]: event.target.value
});
}
And use like this;
<input handleChange ={this.handleChange("phone")} value={this.state.phone}/>
<input> elements often have a property called name.
We can access this name property from the event object that we receive from an event handler:
Write a generalized change handler
constructor () {
super();
this.state = {
name: '',
age: ''
};
this.handleChange = this.handleChange.bind(this);
}
handleChange (evt) {
this.setState({ [evt.target.name]: evt.target.value });
}
render () {
return (
<form>
<label>Name</label>
<input type="text" name="name" onChange={this.handleChange} />
<label>Age</label>
<input type="text" name="age" onChange={this.handleChange} />
</form>
);
}
source
updatePrevData=(event)=>{
let eventName=event.target.name;
this.setState({
...this.state,
prev_data:{
...this.state.prev_data,
[eventName]:event.target.value
}
})
console.log(this.state)
}
You can do it without duplicate code and easy way
handleChange=(e)=>{
this.setState({
[e.target.id]:e.target.value
})
}
<Form.Control type="text" defaultValue={this.props.allClients.name} id="clientName" onChange={this.handleChange}></Form.Control>
<Form.Control type="email" defaultValue={this.props.allClients.email} id="clientEmail" onChange={this.handleChange}></Form.Control>
handleChange(event){
this.setState({[event.target.name]:event.target.value});
this.setState({[event.target.name]:event.target.value});
}

Categories

Resources