I'm making a little blog in React and I have a problem updating the state on input change event.
The warning is:
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
This is my code:
Constructor:
constructor(props){
super(props);
this.state = {
id: '',
post: {
title: '',
slug: '',
content: ''
}
}
this.handleChange = this.handleChange.bind(this);
}
handleChange function
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
post: {
[name]: value
}
});
}
input render:
render(){
return (
<div>
<AdminMenu />
<div id="admin-post-form">
<div className="input-group vertical">
<label>Título</label>
<input
name="title"
placeholder="Título"
type="text"
value={this.state.post.title}
onChange={this.handleChange}
/>
</div>
<div className="input-group vertical">
<label>Slug</label>
<input
name="slug"
placeholder="Slug"
type="text"
value={this.state.post.slug}
onChange={this.handleChange}
/>
</div>
</div>
</div>
)
}
What's wrong with my code ? The field is updated, but I get that warning.
Thanks!
This:
this.setState({
post: {
[name]: value
}
});
will replace this.state.post completely with an object that only has a single key. For example, if name is slug, you will replace post with { slug: 'something' }.
As a result, if you edit one field, all other fields will become undefined. React treats value={undefined} as an uncontrolled component and warns you.
To fix the issue, you probably want to merge post updates with the existing object instead of replacing it:
this.setState(prevState => ({
post: {
...prevState.post,
[name]: value
}
}));
Your set state is resetting the whole post object. You likely want to do something like:
this.setState({
post: {
...this.state.post
[name]: value
}
})
Solved using spread operator, this is the updated handleChange function that works with nested property:
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
var post = {...this.state.post}
post[name] = value;
this.setState({post});
}
Related
An error appears when entering any value
At first I used class components, but then I started redoing them for functional ones and everything broke.
In my state I get a value like this:title > title:"[object Object] and the last symbol which I entered.
Here is the code
reducer
export const postsReducer = (state = initialState, action) => {
switch (action.type) {
case CREATE_POST:
return {...state, posts: state.posts.concat(action.payload)}
default:return state
}
}
Action
export function createPost(post){
return{
type: CREATE_POST,
payload:post
}
}
and a function in a class component
this.setState(prev => ({
...prev, ...{
[event.target.name]: event.target.value
}
}))
so I converted it into a functional one. In setTitle I store the value const [title, setTitle] = useState('');
setTitle(prev => ({
...prev, ...{
[event.target.name]:event.target.value
}
}))
This depends on how you are referencing value on your input. Given your current setTitle operation, if you are referencing title like:
<input type="text" name="title" onInput={handleInput} value={title} />
The problem is that you are turning title into an object with your setTitle operation. An object with property "title" such as { title: "some text" }. That then get's stringified into [object Object].
You could change setTitle to the following to keep it as a flat string:
setTitle(e.target.value)
Or you could change the structure of your state to be an object of form properties:
// create an object with properties to hold your form values
const [form, setForm] = useState({ title: '' });
function handleInput(e) {
setForm(prev => ({
...prev,
[e.target.name]: e.target.value,
}));
}
// reference specific property on form state object
<input type="text" onInput={handleInput} value={form.title} />
Hopefully that helps!
I am using Gatsby. This is my code:
import React from "react"
class ContactCard extends React.Component {
constructor(props) {
super(props);
this.state = { form: { name: "test" }, message: ""};
this.handleChange = this.handleChange.bind(this);
}
handleSubmit = e => {
e.preventDefault();
};
handleChange = e => {
console.log("handleChange: " + e.target.name + " = " + e.target.value);
this.setState({ [e.target.name]: e.target.value, message: "event value: " + e.target.value });
/*
I also tried the following:
this.setState({ name: e.target.value, message: "event value: " + e.target.value });
*/
}
render() {
const { form: { name }, message } = this.state;
return (
<>
<p>{message} name: {name} </p>
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={name}
name="name"
onChange={this.handleChange}
/>
</form>
</>
);
}
}
export default ContactCard
When I type in the input box I see the following in the console log:
handleChange: name = testg
And the P tag changes to have the following text:
event value: testg name: test
The input box's value does not change it remains the same no matter what I do. As commented in my code, I also tried to setState on name directly instead of using the event name, that did not work. I also tried the following:
handleChange = e => {
console.log("handleChange: " + e.target.name + " = " + e.target.value);
var newState = { [e.target.name]: e.target.value, message: "event value: " + e.target.value };
this.setState(newState);
}
This results in the same behavior. What am I missing?
Issue
You are incorrectly setting state. Your state shape has name as a property of state.form, so the setState needs to match this shape.
Solution
Nest [e.target.name]: e.target.value in the form object. Ensure you shallow copy any existing form state as well. The React setState will shallow merge the root object, but it won't do a deep merge of nested state updates.
Since react state updates are asynchronous and (currently in react 16.x) react synthetic events are typically quickly nullified and returned to the event pool, you may also want to save the event name and value properties before enqueueing the state update.
handleChange = (e) => {
const { name, value } = e.target;
this.setState((prevState) => ({
form: {
...prevState.form,
[name]: value
},
message: "event value: " + value
}));
};
So you are updating the new state incorrectly.
You should be nesting name within the form property.
Sending from a mobile device, Pardon my formatting.
Can you try binding the value of input to this.state.form.name instead like so:
<input
type="text"
value={this.state.form.name}
name="name"
onChange={this.handleChange}
/>
I am trying to use state to hide Semantic-UI Model form also to pass JSON to Customer class to add new customer. I am able to pass the value but in the end there is only one value.
When I start typing name, in console panel on 1st character name is empty "" and on 2nd character there is single "a"
In address
on create button click
when i click create button the name is empty"" and address has value.
import React from 'react';
import { Button, Form, Modal } from 'semantic-ui-react';
export default class AddCustomer extends React.Component {
constructor(props) {
super(props);
this.state = {
showCreateForm:false,
formData:{
name: '',
address: ''
}
}
this.handleChangeName = this.handleChangeName.bind(this);
this.handleChangeAddress = this.handleChangeAddress.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChangeName(event) {
const value = event.target.value;
console.log(value);
this.setState({formData:{name:value}});
//when i go to add input the formData is still empty name is empty.
//name: ""
//address: ""
console.log(this.state.formData);
}
handleChangeAddress(event) {
const value = event.target.value;
console.log(value);
this.setState({formData:{address:value}});
//name: "ram" but now there is no address in formData
console.log(this.state.formData);
}
handleSubmit(event) {
event.preventDefault();
////address: "aaaaa" now there no name in formData
console.log(this.state.formData);
this.setState({formData:{
name:this.state.name, address:this.state.address
}});
this.props.onAddFormSubmit(this.state.formData);
}
//On cancel button click close Create user form
closeCreateForm = () => {
this.setState({ showCreateForm: false })
}
//Open Create new Customer form
openCreateCustomer = () => {
this.setState({ showCreateForm: true })
}
render() {
return (
<div>
<Modal closeOnTriggerMouseLeave={false} trigger={
<Button color='blue' onClick={this.openCreateCustomer}>
New Customer
</Button>
} open={this.state.showCreateForm}>
<Modal.Header>
Create customer
</Modal.Header>
<Modal.Content>
<Form onSubmit={this.handleSubmit}>
<Form.Field>
<label>Name</label>
<input type="text" placeholder ='Name' name = "name"
value = {this.state.name}
onChange = {this.handleChangeName}/>
</Form.Field>
<Form.Field>
<label>Address</label>
<input type="text" placeholder ='Address' name = "address"
value = {this.state.address}
onChange = {this.handleChangeAddress}/>
</Form.Field>
<br/>
<Button type='submit' floated='right' color='green'>Create</Button>
<Button floated='right' onClick={this.closeCreateForm} color='black'>Cancel</Button>
<br/>
</Form>
</Modal.Content>
</Modal>
</div>
)
}
}
With these lines you are replacing your whole state with the value of one field:
Your initial state:
state = {
formData: {
name: "",
address: ""
}
}
Your state after this line:
this.setState({formData:{address:value}});
state = {
formData: {
address: ""
}
}
And with this line:
this.setState({formData:{name:value}});
state = {
formData: {
name: ""
}
}
As mentioned in the answer by Greg b, you have to change the value of the specific key and copy rest as they are. You can also use spread operator to copy rest of the unmodified fields easily.
this.setState({...this.state.formData, address: value})
will only change the address and copy rest of the state as it is. This comes handy when you have multiple fields in the state.
This is your state
formData:{
name: '',
address: ''
}
Change both these lines respectively
this.setState({formData:{address:value}});
this.setState({formData:{name:value}});
To
this.setState({formData:{name: this.state.formData.name, address:value}});`
this.setState({formData:{name:value, address: this.state.formData.address}});`
function handleChangeName(event) {
const value = event.target.value;
this.setState((prevState)=>({ formData: { name: value, address: prevState.formData.address }}),
()=>{ console.log(this.state.formData)});
}
function handleChangeAddress(event) {
const value = event.target.value;
this.setState((prevState)=>({ formData: { name: prevState.formData.name, address: value}}),
()=>{ console.log(this.state.formData)});
}
if you what to something next after setState() or state update, you have to use call back function.
State Updates are Merged
It looks like you aren't accessing the state correctly in the render(), I see you doing this.state.name and this.state.address rather than this.state.formData.nameand this.state.formData.address.
Also you probably shouldn't be storing your form data in a single state object, break it out into a field for each input, e.g.
this.state = {
showCreateForm: false,
name: "...",
address: "..."
}
You cannot update only part of an object in state, you need to update the whole thing. When you do this.setState({formData:{address:value}}); or are essentially setting this.state.formData.name to undefined, since the object is now going to only be { address: value }
I'm pretty new to to TypeScript so I'm looking for a graceful way to solve the following issue. I have a state variable called emailAddress and the value of this variable is set by an input field. I also want the input field to get it's value in return from the state variable, as such:
interface MyState {
emailAddress: string;
}
(...)
constructor(props: LandingProps) {
super(props);
this.state = {
emailAddress: "",
}
}
(...)
<input
type="text"
placeholder="YourName#Example.com"
value={ emailAddress }
onChange = { (e: React.ChangeEvent) => this.setState({ emailAddress: e.target.nodeValue }) }
/>
The issue here is that the value of the input field cannot be null eventhough the value of e.target.nodeValue can be null
The solution ended up being this:
onChange = { (e: React.ChangeEvent<HTMLInputElement>) => this.setState({emailAddress: e.target.value})}
We missed the TypeVariable: HTMLInputElement
I'm trying to display an error in a form field by adding a className.
This is the render function:
render() {
return (
<div className="row row--no-margin">
<button onClick={this.validate}>Test validation</button>
{
this.props.model.map( (field, index) => {
return this.renderTextField(field);
});
}
</div>
);
}
This is the renderTextField function:
renderTextField(field, index) {
let inputClassNames = 'form-control';
if (this.state.errors.indexOf(field.name) !== -1) {
inputClassNames += ' error-required';
}
return (
<div className={field.wrapperClassName} key={field.key}>
<label className="field-label">{field.label}</label>
<input
type="text"
name={field.name}
ref={field.name}
className={inputClassNames}
onChange={this.handleChange}
value={this.state[field.name]}
/>
</div>
);
}
When i click the button to test validation, the class "error-required" is added to the input, but as soon as i type anything, it loses the class.
This is the onChange function:
handleChange(event) {
this.setState({
[event.target.name] : event.target.value
});
}
The field gets its data from an object:
{
key : 'name',
name : 'name',
type : 'text',
label : 'Full Name',
wrapperClassName: 'col-md-6',
},
Am i missing something?
EDIT:
validate function:
validate() {
let errors = [];
this.props.model.map((m, index) => {
if(!this.state[m.name]){
errors.push(m.name);
}
});
this.setState({
errors: errors
})
}
I would suggest separating the form's "field state", from your "validation state", to avoid potential conflicts in the case that you have a field with name "error".
If your form has a field with name "error", changing it's value will cause your validation state to be replaced, and will produce errors/unexpected results.
Consider making the following adjustments:
// in renderTextField() use this.state.form[field.name]
<input
type="text"
name={field.name}
ref={field.name}
className={inputClassNames}
onChange={this.handleChange}
value={this.state.form[field.name]}
/>
And in handleChange(event) consider revising it to:
handleChange(event) {
const form = { ...this.state.form, [event.target.name] : event.target.value }
this.setState({
form : form
})
}
Note, you will also need to initialise your component state to include/define the form object to track the state of fields.