Form update in React : input previous data is erased if the input is left empty when submitted - javascript

So, I have a data persistence issue with my form inputs.
If I modify all inputs everything is fine.
But if an input is left empty, its previous data is erased when I submit. I need suggestions for my handleChange to keep data even when an input is not modified.
I tried this but it failed too :
handleChange = e => {
e.persist();
this.setState(prevState => ({
product: { ...prevState.product, [e.target.name]: e.target.value }
}))
}
Here is my EditForm, thanks for your help.
EditForm.js
export default class EditForm extends Component {
constructor(props) {
super(props);
this.state = { product: [] };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
};
componentDidMount = () => {
axios
.get(`/products/edit-form/${this.props.match.params.id}`)
.then(response => {
console.log(response.data.products);
this.setState({
product: response.data.products
})
});
};
handleChange(e) {
console.log(e.target.name);
this.setState({[e.target.name]: e.target.value})
}
handleSubmit(e) {
const data = {
id: this.props.match.params.id,
reference: this.state.reference,
designation: this.state.designation
}
e.preventDefault();
console.log(data);
axios
.post(`/products/${data.id}`, data )
.then(res => console.log(res))
.catch(err => console.log(err));
};
renderForm() {
return this.state.product.map((product, index) => {
const { id,reference,designation } = product
return(
<>
<Form className="post" onSubmit={this.handleSubmit}>
<Form.Row>
<Form.Group as={Col} controlId="formGridReference">
<Form.Label>Reference</Form.Label>
<Form.Control type="text" value={this.state.product.reference}
onChange={this.handleChange} name="reference" placeholder={reference}/>
</Form.Group>
<Form.Group as={Col} controlId="formGridDesignation">
<Form.Label>Designation</Form.Label>
<Form.Control type="text" value={this.state.product.designation}
onChange={this.handleChange} name="designation" placeholder={designation}/>
</Form.Group>
</Form.Row>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
</>
);
})
}
render() {
return (
<div>
<h1>Formulaire de modification</h1>
{this.renderForm()}
</div>
);
}
}```

In your EditForm.js, inside handleChange(), you have to maintain your previous state. You can do so by creating a copy of your state. Then use the copy to update your state property values and in the end, use this.setState();
Example:
const state = this.state;
state['Your property'] = 'value';
...
...
this.setState(state);

Related

Uncontrolled input React

I have the following code:
import React, { Component } from 'react'
import axios from 'axios'
import Navbar from '../Navbar'
import { Avatar, TextField, Button, Container, CircularProgress } from '#material-ui/core'
import Alert from '#material-ui/lab/Alert'
class PrivateProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
id: null,
image: null,
pp: null,
username: 'AnonymousUser',
showSuccess: false
}
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleFileChange = this.handleFileChange.bind(this)
}
componentDidMount() {
axios.get('http://127.0.0.1:8000/users/profile')
.then(res => {
this.setState({
user: res.data,
id: res.data.id,
username: res.data.username,
pp: res.data.pp
})
})
.catch(err => console.log(err))
}
handleSubmit(e) {
e.preventDefault()
const fd = new FormData()
fd.append('pp', this.state.image)
fd.append('username', this.state.user.username)
fd.append('email', this.state.user.email)
fd.append('bio', this.state.user.bio)
const d = {
pp : this.state.image,
username : this.state.user.username,
email : this.state.user.email,
bio : this.state.user.bio
}
console.log('d', d)
console.log('fd', fd)
axios.put(`http://127.0.0.1:8000/users/profile/update/${this.state.id}/`, fd, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(res => {
this.setState({
user: res.data,
id: res.data.id,
pp: res.data.pp,
image: null,
username: res.data.username,
showSuccess: true
})
})
.catch(err => console.log(err))
}
handleChange(e) {
this.setState({
user: {
[e.target.name]: e.target.value
}
})
}
handleFileChange(e) {
this.setState({image: e.target.files[0]})
}
render() {
let message
let alert
if (this.state.user !== null) {
if (!this.state.user.bio) {
message = <h4>Please update your profile below.</h4>
}
if (this.state.showSuccess) {
alert = <Alert action={<Button onClick={() => this.setState({showSuccess: false})}>Close</Button>} severity='success'>Profile Successfully Updated</Alert>
}
return (
<div>
<Navbar />
<Container style={{background: '#f7f4e9'}}>
<div style={{height: '60px'}}></div>
<h2>Your Profile</h2>
<Avatar src={this.state.user.pp} alt={this.state.user.username} />
{message}
{alert}
<h4>Your data:</h4>
<form onSubmit={this.handleSubmit}>
<p>Profile Pic</p>
<input type="file" onChange={this.handleFileChange}/>
<br></br>
<br></br>
<TextField label='Username' name="username" onChange={this.handleChange} type="text" value={this.state.user.username} />
<br></br>
<br></br>
<TextField label='Email' name="email" onChange={this.handleChange} type="email" value={this.state.user.email} />
<br></br>
<br></br>
<TextField label='Bio' name="bio" onChange={this.handleChange} type="text" value={this.state.user.bio} />
<br></br>
<br></br>
<br></br>
<Button type="submit" value="submit">Update</Button>
</form>
</Container>
</div>
)
} else {
return <CircularProgress />
}
}
}
export default PrivateProfile
I get the error saying: 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.
Can someone help me fix it.
Since you're initializing state values with null and using it like value={this.state.user.username}, and update the state, you'll get such error:
Warning: A component is changing a controlled input of type text to be uncontrolled.
To control it's state, use it like:
value={this.state.user.username || ''}
As per my comment, you have issue here:
handleChange(e) {
this.setState({
user: {
[e.target.name]: e.target.value
}
})
}
The user state will always change on your any input changes, you will need like:
handleChange(e) {
this.setState({
user: {
...this.state.user,
[e.target.name]: e.target.value
}
})
}

How to write a generic method to handle multiple state changes in React

I'm building an exercise tracker app in React.
Right now, I'm building the CreateExercise component to submit a form, so I need to update the states of each value. In order to do so, I created methods to handle those changes (onChangeUsername, onChangeDescription, onChangeDuration etc...) but I don't really like to repeat methods like this.
How to write a more generic method to handle this task ?
class CreateExercise extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
description: '',
duration: 0,
date: new Date(),
users: []
}
}
onChangeUsername = (e) => {
this.setState({
username: e.target.value
});
}
onChangeDescription = (e) => {
this.setState({
description: e.target.value
});
}
onChangeDuration = (e) => {
this.setState({
duration: e.target.value
});
}
onChangeDate = (date) => {
this.setState({
date: date
});
}
onSubmit = (e) => {
e.preventDefault();
const exercise = {
username: this.state.username,
description: this.state.description,
duration: this.state.duration,
date: this.state.date
}
console.log(exercise);
window.location = '/';
}
render() {
return(
<div>
<h3>Create New Exercise Log</h3>
<form onSubmit={ this.onSubmit }>
<div className='form-group'>
<label>Username:</label>
<select
ref='userInput'
required
className='form-control'
value={ this.state.username }
onChange={ this.onChangeUsername }
>
{ this.state.users.map((user) => (
<option key={user} value={user}>{user}</option>
))
}
</select>
</div>
<div className='form-group'>
<label>Description:</label>
<input
type='text'
required
className='form-control'
value={ this.state.description }
onChange={ this.onChangeDescription}
/>
</div>
<div className='form-group'>
<label>Duration:</label>
<input
type='text'
className='form-control'
value={ this.state.duration }
onChange={ this.onChangeDuration }
/>
</div>
<div className='form-group'>
<label>Date:</label>
<div>
<DatePicker
selected={ this.state.date }
onChange={ this.onChangeDate }
/>
</div>
</div>
<div className='form-groupe'>
<input
type='submit'
value='Create Exercise Log'
className='btn btn-primary'
/>
</div>
</form>
</div>
);
}
}
export default CreateExercise;
Using partial application, create a function in your component that takes a field name, and returns a function that sets the state:
onChangeValue = field => e => {
this.setState({
[field]: e.target.value
});
};
Usage:
onChangeUsername = onChangeValue('username');
onChangeDescription = onChangeValue('description');
onChangeDuration = onChangeValue('duration');
You extend the idea further to support the onChangeDate as well:
onChangeValue = (field, valueTransformer = e => e.target.value) => e => {
this.setState({
[field]: valueTransformer(e.target.value)
});
};
This doesn't change the other on functions, since the default is to get e.target.value. To use onChangeDate we can now change the valueTransformer:
onChangeDate = onChangeValue('date', v => v);
You can define name for the HTML element, and use that to set value:
onChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
corresponding JSX element:
<input
type="text"
name="description"
required
className="form-control"
value={this.state.description}
onChange={this.onChange}
/>

How to filter the data from state depending on the value of the input

I'm testing at the same page at the moment, later I will move it into other components. I have an input field that sends its value to the state, also I'm fetching data from a json to the state, and I'm displaying all the information from the state but I want to only display the information that has the same email as the input field. I'm sending it with a button.
I'm just needing like a function of a way to filter the display but i can't understand how to do it.
class Testing extends Component {
state = { data: [], value: "", filteredData: "" };
handleSubmit = this.handleSubmit.bind(this);
handleChange = this.handleChange.bind(this);
async componentDidMount() {
await fetch("http://tenjoucesar.github.io/data.json")
.then(response => response.json())
.then(data => {
this.setState({ data });
});
}
handleSubmit(e) {
e.preventDefault();
let email = this.state.value;
fetch("https://tenjoucesar.github.io/data.json")
.then(response => response.json())
.then(data => {
let result = data.filter(person => person.email === email);
this.setState({ filteredData: result });
});
}
handleChange(e) {
this.setState({ value: e.target.value });
}
Everything here works, Form "is on the same jsx
<form onSubmit={this.handleSubmit}>
<input
type="email"
value={this.state.value}
onChange={this.handleChange}
/>
<input
type="submit"
value="Submit"
/>
</div>
</form>
How do i display the data?
<ul>
{data.map(person => {
return (
<div className="result" key={person.email}>
<h3 className="heading-tertiary">
{person.name}, {person.age}
</h3>
<p className="paragraph--result">{person.notes}</p>
);
})}
I am displaying everything, also sending the value to the state, how can i only display the data with the same email? Thank you!
I hope this help you. you were calling two times api for data. I have change it for better performance.
'use strict';
class Testing extends React.Component {
state = { data: [], value: "", filteredData: [] };
handleSubmit = this.handleSubmit.bind(this);
handleChange = this.handleChange.bind(this);
async componentDidMount() {
await fetch("http://tenjoucesar.github.io/data.json",{mode: 'cors'})
.then(response => response.json())
.then(data => {
this.setState({ data });
});
}
handleSubmit(e) {
e.preventDefault();
let email = this.state.value;
const filtered = this.state.data.filter((e) => e.email == email);
this.setState({
filteredData:filtered
})
}
handleChange(e) {
this.setState({ value: e.target.value });
}
InfomData = (person) => {
return (
<div className="result" key={person.email}>
<h3 className="heading-tertiary">
{person.name}, {person.age}
</h3>
<p className="paragraph--result">{person.notes}</p>
</div>
)
}
render () {
const dataToRender = this.state.filteredData.map(
(e) => this.InfomData(e)
)
return (
<div>
<form onSubmit={this.handleSubmit}>
<input
type="email"
value={this.state.value}
onChange={this.handleChange}
/>
<input
type="submit"
value="Submit"
/>
</form>
{dataToRender}
</div>
)
}
}
ReactDOM.render(
<Testing />,
document.getElementById('root')
)
let result = []
data.forEach(person => {
if(person.email === 'targetemail#whatever.com')
{ result.push(
<div className="result" key={person.email}>
<h3 className="heading-tertiary">
{person.name}, {person.age}
</h3>
<p className="paragraph--result">{person.notes}</p>
</div>)
}
}

How to update the state of array of objects from form fields?

I have created a component that can be used for creating a new company record. A modal is opened with a form and the values are linked to the state values. In my situation, it will be possible to create more than one record of a company if the user chooses to add another company. A new company object will be pushed to the company state and the new empty form will be rendered.
This is what I've tried based on this answer:
import { Component } from 'react';
import { Modal, Header, Form, Button, Icon, Tab, Segment } from 'semantic-ui-react';
export default class CompanyCreate extends Component {
constructor(props) {
super(props);
this.state = {
company: [
{
name: '',
segment: ''
}
]
};
this.initialState = this.state;
this.handleChange = this.handleChange.bind(this);
this.handleCompanyChange = this.handleCompanyChange.bind(this);
}
handleChange = (e, { name, value }) => this.setState({ [name]: value });
handleCompanyChange = (e, { name, value }) => {
const index = this.state.company.findIndex((x) => {
return x[name] === value;
});
if (index === -1) {
console.log('error');
} else {
this.setState({
company: [
...this.state.company.slice(0, index),
Object.assign({}, this.state.company[index], value),
...this.state.company.slice(index + 1)
]
});
}
};
render() {
const { company } = this.state;
return (
<Segment>
{company.map((e, index) => (
<Form size="large" key={index}>
<Form.Group>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Nome"
placeholder="Nome"
name="name"
value={e.name}
required
/>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Segmento"
placeholder="Segmento"
name="segment"
value={e.segment}
required
/>
</Form.Group>
</Form>
))}
</Segment>
);
}
}
My problem is that I can't set the company state properly. How can you update the state in relation to the changes in the form fields?
Looking for answers, I found the package: immutability-helper. Based on this answer, the problem was solved simply and elegantly.
The solution:
import update from 'immutability-helper';
//...
this.state = {
company: [
{
name: '',
segment: ''
}
]
};
//...
handleCompanyChange = (e, { name, value, id }) => {
let newState = update(this.state, {
company: {
[id]: {
[name]: { $set: value }
}
}
});
this.setState(newState);
};
//...
render() {
const { company } = this.state;
return (
<Segment>
{company.map((e, index) => (
<Form size="large" key={index}>
<Form.Group>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Nome"
placeholder="Nome"
name="name"
value={e.name}
id={index}
required
/>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Segmento"
placeholder="Segmento"
name="segment"
value={e.segment}
id={index}
required
/>
</Form.Group>
</Form>
))}
</Segment>
);
}

state is cleared, but input field text is not after form is submitted in React

I am doing exercise on a simple todo App, the user can type their todos in the field, and hit submit to see it added to the todo-list.
I've managed to clear the state once the form is submitted with 'this.setState({newTodo: ''})' (indicated by hitting submit again will add an empty todo-item);
however, the text in the input field will not be cleared.
const TodoItem = ({ text }) => <li>{text}</li>;
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: ['walk dog', 'feed cat'],
newTodo: ''
};
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
e.preventDefault();
const todos = [...this.state.todos, this.state.newTodo];
this.setState({ todos, newTodo: '' });
}
render() {
const { newTodo } = this.state;
const todos = this.state.todos.map((todo, index) => <TodoItem key={index} text={todo} />);
return (
<div className="App">
<form onSubmit={this.handleSubmit}>
<h1>Simple Todo App</h1>
<input
type="text"
name="newTodo"
value={this.newTodo}
onChange={e => this.setState({ [e.target.name]: e.target.value })}
/>
<ol>
{todos}
</ol>
<button>SAVE</button>
</form>
</div>
);
}
}
export default App;
Thanks for any kind of help.
this.newTodo is undefined, use this.state.newTodo instead od this.newTodo :
<input
type="text"
name="newTodo"
value={this.state.newTodo}
onChange={e => this.setState({ [e.target.name]: e.target.value })}
/>
OR:
const { newTodo } = this.state;
<input
type="text"
name="newTodo"
value={newTodo}
onChange={e => this.setState({ [e.target.name]: e.target.value })}
/>
The reason you see empty newTodo added because your newTodo initial state is empty and in handleSubmit you are always passing it irrespective of whether it’s is empty or not. So In handleSubmit check newTodo state and then add newTodo to todos array.
if(this.state.newTodo != “”){
const todos = [...this.state.todos, this.state.newTodo];
}
and change input field value attribute value to newTodo
<input value={newTodo} />
Don’t use this.newTodo
In the following section:
<input
type="text"
name="newTodo"
value={this.newTodo}
onChange={e => this.setState({ [e.target.name]: e.target.value })}
/>
Change value={this.newTodo} to value={this.state.newTodo}

Categories