React controlled component error in function component - javascript

I am working with forms in React, using function components for the first time. Either I am going crazy, or this should work with no issues...
import React, {useEffect, useState} from 'react';
function ChangePasswordComponent(props) {
const {onChangePassword} = props;
const [isValid, setIsValid] = useState(true);
const [form, setForm] = useState({
password: undefined,
confirm: undefined
})
useEffect(() => {
handleValidation();
}, [form])
function handleValidation() {
setIsValid(form.password === form.confirm);
}
function onFormValueChanges(event) {
setForm({...form, [event.target.name]: event.target.value})
}
function resetFields() {
setForm({
password: undefined,
confirm: undefined
})
}
function onUpdateClick() {
onChangePassword(form.password);
resetFields();
}
return (
<div className="change-password-container">
<input
type="text"
name="password"
value={form.password}
onChange={(event) => onFormValueChanges(event)}
placeholder="new password" />
<input
type="text"
name="confirm"
value={form.confirm}
onChange={(event) => onFormValueChanges(event)}
placeholder="confirm new password" />
{!isValid ?
<span className="validation-error">passwords do not match</span> : null }
<div className="button-container">
<button onClick={() => resetFields()}>Cancel</button>
<button onClick={() => onUpdateClick()}
disabled={!form.password || !isValid}>Update</button>
</div>
</div>
);
}
export default ChangePasswordComponent;
However when I run the code I get an error in console about...
A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
When I look back into the documentation, my pattern seems to follow the Docs just fine. Thoughts?

you should use empty strings like '' instead of undefined.
when your component has first rendered, password and confirm values are undefined. This means that there is no variable to set the value attribute of the input.
Therefore, that error occurs.

Related

React Function Component Form not setting state variables?

I have a React Function Component that is a form where users enter in values. The user inputs are stored using Hooks. The issue that I am running into is that it does not seem to be setting the hook correctly. When I console log one value right before the axios call, it returns as undefined. Below is the code I have so far. I think I am pretty close but am unsure where I made mistakes. Any help is appreciated!
import React, { useState, useEffect } from "react";
import axios from "axios";
function ReportOutage(){
//Use state hook to hold the values users input, passed into axios call
//setting the default values
const [formData, setFormData] = useState({
userReport: '6',
serviceType: "",
serviceName: "",
serviceStreet: "",
serviceCity: "",
serviceState: "",
serviceDescription: ""
})
const handleChange = (event) => {
event.preventDefault();
setFormData({[event.target.name]: event.target.value})
};
const handleSubmitReport = async (event) =>{
event.preventDefault();
console.log(formData.serviceName); // This is returning undefined when I am expecting the service name entered by the user.
const res = await axios.post("/outage-new", {
user_id: `${formData.userReport}`,
service_type: `${formData.serviceType}`,
service_name: `${formData.serviceName}`,
outage_street: `${formData.serviceStreet}`,
outage_city: `${formData.serviceCity}`,
outage_state: `${formData.serviceState}`,
outage_description: `${formData.serviceDescription}`,
})
};
return (
<>
<h1 id="Report-Title" class>Test Dialog box</h1>
<form onSubmit={handleSubmitReport}>
<input type="text" placeholder="Service Type"
onChange={handleChange}
value={formData.serviceType}
name="serviceType"/>
<input type="text" placeholder="Service Name"
onChange={handleChange}
value={formData.serviceName}
name="serviceName"/>
<input type="text" placeholder="Street"
onChange={handleChange}
value={formData.serviceStreet}
name="serviceStreet"/>
<input type="text" placeholder="City"
onChange={handleChange}
value={formData.serviceCity}
name="serviceCity"/>
<input type="text" placeholder="State"
onChange={handleChange}
value={formData.serviceState}
name="serviceState"/>
<input type="text" placeholder="Description"
onChange={handleChange}
value={formData.serviceDescription}
name="serviceDescription"/>
<button type="submit">Report Outage</button>
</form>
</>
);
}
export default ReportOutage;
When using the useState hook, updates are not shallowly merged like they are in the class component's this.setState. You are replacing the state with a new object with only the last field value updated.
const handleChange = (event) => {
event.preventDefault();
setFormData({ // <-- new object with only field name/value
[event.target.name]: event.target.value
});
};
You must manage this yourself, manually. Use a functional state update to access and shallow copy the previous state into the next state object.
const handleChange = (event) => {
event.preventDefault();
const { name, value } = event.target;
setFormData(data => ({
...data,
[name]: value,
}));
};

React js - useState returns different values inside and outside of a onChange Form function

I would like to know why loginPassword.length and loginPasswordError is different inside and outside of loginFormPasswordHandler
import React, {useState} from 'react';
import './styles.css'
const App = () => {
const [loginPassword, setLoginPassword] = useState('');
const [loginPasswordError, setLoginPasswordError] = useState();
const [submitController, setSubmitController] = useState(false);
const loginFormSubmitHandler = (e) => {
e.preventDefault();
}
const loginFormPasswordHandler = (e) => {
setLoginPassword(e.target.value);
setLoginPasswordError(loginPassword.length < 8);
console.log('login password length is(inside):'+loginPassword.length+' and the state is '+loginPasswordError)
loginPassword.length > 8 ? setSubmitController(true) : setSubmitController(false);
}
console.log('login password length is(outside):'+loginPassword.length+' and the state is '+loginPasswordError)
return(
<React.Fragment>
<div className="form-wrapper">
<form onSubmit={loginFormSubmitHandler}>
<input className={`${loginPasswordError && 'error'}`} type="password" id="password" name="password" placeholder="Password" onChange={loginFormPasswordHandler} />
<div className={`submit-btn ${submitController ? '' : 'disable'}`}>
<input type="submit" />
</div>
</form>
</div>
</React.Fragment>
);
}
export default App;
I know useState re-runs the entire code when the state is changed. But I can't understand this behavior. I am not sure whether this is a Javascript property or React property.
setState is asynchronous, meaning your login password and error state values might not update immediately after you run setLoginPassword and setLoginPasswordError.
The other line below re-runs on every render, so it will output up to date values.
console.log('login password length is(outside):'+loginPassword.length+' and the state is '+loginPasswordError)

Is there a way to get a React component's internal values when I click a button on the parent?

Suppose I have a component like this -
const MyForm = ({ formId }) => (
<div>
<input type="text" placeholder="Full name"></input>
<input type="text" placeholder="Email"></input>
</div>
)
export default MyForm;
And then I have my App.js like so -
import React from "react";
import MyForm from "./MyForm";
const App = () => (
<div id="app">
<MyForm formId="formOne"></MyForm>
<MyForm formId="formTwo"></MyForm>
<button onClick={
() => {
// Here, when the user clicks the button,
// I want to get values of both the textboxes,
// from both the component instances
}
}>Submit</button>
</div>
)
export default App;
So basically, what I want is - when the button is clicked, I want to be able to retrieve the values of the textboxes. One way to do this is to raise an event from inside MyForm.js so that every text change is bubbled up to the parent via a callback function prop, but that feels too cumbersome, especially if the form has a lot of fields. Is there any simple or direct way to do this? Do I need to involve global state management tools like Redux?
State inside a component is specific only to that component, the parent , children or sibling of a component have no idea of the state. The only way to communicate the value from one component to another component is via props . In your case, what we need is a state to reside at the App which can then be passed as a prop to both the MyForm Components.
App.js
const [ formState, setFormState ] = useState({ formOne: {fullName: '', Email: ''}, formTwo: '' })
const updateFormValues = (formId, key, value) => {
const stateCopy = JSON.parse(JSON.stringify(formState));
const formToUpdate = stateCopy[formId];
formToUpdate[key] = value;
setFormState(stateCopy)
}
<MyForm formId="formOne" values={formState.formOne} updateFormValues={updateFormValues}></MyForm>
<MyForm formId="formTwo" values={formState.formTwo} updateFormValues={updateFormValues}></MyForm>
MyForm.js
const MyForm = ({ formId, values, updateFormValues }) => {
const onInputChange = (e, key) => {
updateFormValues(formId, key, e.target.value)
}
return(
<div>
<input type="text" onChange={(e) => onInputChange(e, 'fullName'} value={values.fullName} placeholder="Full name"></input>
<input type="text" onChange={(e) => onInputChange(e, 'email'} value={values.email} placeholder="Email"></input>
</div>
)}
export default MyForm;
To have access to data inside children components you need to lift the state to the parent component.
One-way data flow
Identify every component that renders something based on that state.
Find a common owner component (a single component above all the components that need the state in the hierarchy).
Either the common owner or another component higher up in the hierarchy should own the state.
If you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.
One way to do this:
import React, { useState } from "react";
function MyForm(props) {
const { handleChange, values } = props;
return (
<div>
<label htmlFor="name">Your name</label>
<input
type="text"
placeholder="Full name"
onChange={handleChange}
value={values.name}
id="name"
name="name"
/>
<label htmlFor="email">Your email</label>
<input
type="email"
placeholder="Email"
onChange={handleChange}
value={values.email}
id="email"
name="email"
/>
</div>
);
}
function App() {
const [values, setValues] = useState({ name: "", email: "" });
const handleChange = (event) => {
const updatedForm = { ...values, [event.target.name]: event.target.value };
setValues(updatedForm);
};
return (
<div id="app">
<MyForm
formId="formOne"
values={values}
handleChange={handleChange}
></MyForm>
<button
onClick={() => {
console.log(values);
}}
>
Submit
</button>
</div>
);
}
export default App;

using ...prev in setState prohibits userEvent.type to type into input when react testing

I want to test my Log In Component, which consists of two input fields, whose values are determined by a single react state that is an object with two parameters. However, when I try the test only the first letter appears in the value of the selected input and not the rest of the word. I determined my use of ...prev when updating the state to be the issue. If I only use a single input field with one state it works fine!
Here is my component:
import {useState} from 'react';
export function Login () {
//Login Credentials
const [loginCredentials, setLoginCredentials] = useState({ name: '' });
const handleChange = ({target}) => {
setLoginCredentials({[target.name]: target.value});
}
return (
<div className="login-container">
<h1>Log In</h1>
<div className="login-label-input">
<label htmlFor="name">Account Name
<input
type="name"
id="name"
name="name"
onChange={handleChange}
value={loginCredentials.name}
/>
</label>
<label htmlFor="name">Password
<input
type="password"
id="password"
name="password"
onChange={handleChange}
value={loginCredentials.password}
/>
</label>
</div>
State name: {loginCredentials.name}. State password: {loginCredentials.password}.
</div>
)
}
This works but if I include the password state:
export function Login () {
//Login Credentials
const [loginCredentials, setLoginCredentials] = useState({ name: '', password: '' });
const handleChange = ({target}) => {
setLoginCredentials((prev) => ({...prev, [target.name]: target.value}));
}
...
it does not pass the test. I does not throw an error but simply only adds the first letter of the string I am testing with:
test("log in with empty name input returns error message", async () => {
render(
<Login />
);
const nameField = screen.getByLabelText(/account name/i);
userEvent.type(nameField, 'test');
await waitFor(() => expect(nameField).toHaveValue('test'));
});
with the error:
expect(element).toHaveValue(test)
Expected the element to have value:
test
Received:
t
Is using ...prev bad or is this is a bug or what is going on?
It seems like you have to assign the new value to a different variable, I am not sure why this is necessary for the test but not in the app itself.
const handleChange = ({target}) => {
const newValue = target.value
setLoginCredentials((prev) => ({ ...prev, [target.name]: newValue }));
}

A component is changing an uncontrolled input of type text to be controlled error in ReactJS

Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.*
Following is my code:
constructor(props) {
super(props);
this.state = {
fields: {},
errors: {}
}
this.onSubmit = this.onSubmit.bind(this);
}
....
onChange(field, e){
let fields = this.state.fields;
fields[field] = e.target.value;
this.setState({fields});
}
....
render() {
return(
<div className="form-group">
<input
value={this.state.fields["name"]}
onChange={this.onChange.bind(this, "name")}
className="form-control"
type="text"
refs="name"
placeholder="Name *"
/>
<span style={{color: "red"}}>{this.state.errors["name"]}</span>
</div>
)
}
The reason is, in state you defined:
this.state = { fields: {} }
fields as a blank object, so during the first rendering this.state.fields.name will be undefined, and the input field will get its value as:
value={undefined}
Because of that, the input field will become uncontrolled.
Once you enter any value in input, fields in state gets changed to:
this.state = { fields: {name: 'xyz'} }
And at that time the input field gets converted into a controlled component; that's why you are getting the error:
A component is changing an uncontrolled input of type text to be
controlled.
Possible Solutions:
1- Define the fields in state as:
this.state = { fields: {name: ''} }
2- Or define the value property by using Short-circuit evaluation like this:
value={this.state.fields.name || ''} // (undefined || '') = ''
Changing value to defaultValue will resolve it.
Note:
defaultValue is only for the initial load.
If you want to initialize the input then you should use defaultValue, but if you want to use state to change the value then you need to use value. Read this for more.
I used value={this.state.input ||""} in input to get rid of that warning.
Inside the component put the input box in the following way.
<input className="class-name"
type= "text"
id="id-123"
value={ this.state.value || "" }
name="field-name"
placeholder="Enter Name"
/>
In addition to the accepted answer, if you're using an input of type checkbox or radio, I've found I need to null/undefined check the checked attribute as well.
<input
id={myId}
name={myName}
type="checkbox" // or "radio"
value={myStateValue || ''}
checked={someBoolean ? someBoolean : false}
/>
And if you're using TS (or Babel), you could use nullish coalescing instead of the logical OR operator:
value={myStateValue ?? ''}
checked={someBoolean ?? false}
SIMPLY, You must set initial state first
If you don't set initial state react will treat that as an uncontrolled component
that's happen because the value can not be undefined or null to resolve you can do it like this
value={ this.state.value ?? "" }
const [name, setName] = useState()
generates error as soon as you type in the text field
const [name, setName] = useState('') // <-- by putting in quotes
will fix the issue on this string example.
As mentioned above you need to set the initial state, in my case I forgot to add ' ' quotes inside setSate();
const AddUser = (props) => {
const [enteredUsername, setEnteredUsername] = useState()
const [enteredAge, setEnteredAge] = useState()
Gives the following error
Correct code is to simply set the initial state to an empty string ' '
const AddUser = (props) => {
const [enteredUsername, setEnteredUsername] = useState('')
const [enteredAge, setEnteredAge] = useState('')
Set Current State first ...this.state
Its because when you are going to assign a new state it may be undefined. so it will be fixed by setting state extracting current state also
this.setState({...this.state, field})
If there is an object in your state, you should set state as follows,
suppose you have to set username inside the user object.
this.setState({user:{...this.state.user, ['username']: username}})
Best way to fix this is to set the initial state to ''.
constructor(props) {
super(props)
this.state = {
fields: {
first_name: ''
}
}
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.setState({
fields:{
...this.state.fields,
[e.target.name]: e.target.value
}
})
}
render() {
return(
<div className="form-group">
<input
value={this.state.fields.first_name}
onChange={this.onChange}
className="form-control"
name="first_name" // Same as state key
type="text"
refs="name"
placeholder="Name *"
/>
<span style={{color: "red"}}>{this.state.errors.first_name}</span>
</div>
)
}
Then you can still run your checks like if (field) and still achieve the same result if you have the value as ''.
Now since your value is now classified as type string instead of undefined after evaluation. Thus, clearing the error from the console of a big red block 😁😎.
I am new to reactjs and I am using version 17 of reactjs
I was getting this problem
I solved:
Instead of this
const [email, setEmail] = useState();
I added this
const [email, setEmail] = useState("");
In useState function I added quotes to initialize the data and the error was gone.
Put empty value if the value does not exist or null.
value={ this.state.value || "" }
If you're setting the value attribute to an object's property and want to be sure the property is not undefined, then you can combine the nullish coalescing operator ?? with an optional chaining operator ?. as follows:
<input
value={myObject?.property ?? ''}
/>
In my case it was pretty much what Mayank Shukla's top answer says. The only detail was that my state was lacking completely the property I was defining.
For example, if you have this state:
state = {
"a" : "A",
"b" : "B",
}
If you're expanding your code, you might want to add a new prop so, someplace else in your code you might create a new property c whose value is not only undefined on the component's state but the property itself is undefined.
To solve this just make sure to add c into your state and give it a proper initial value.
e.g.,
state = {
"a" : "A",
"b" : "B",
"c" : "C", // added and initialized property!
}
Hope I was able to explain my edge case.
If you use multiple input in on field, follow:
For example:
class AddUser extends React.Component {
constructor(props){
super(props);
this.state = {
fields: { UserName: '', Password: '' }
};
}
onChangeField = event => {
let name = event.target.name;
let value = event.target.value;
this.setState(prevState => {
prevState.fields[name] = value;
return {
fields: prevState.fields
};
});
};
render() {
const { UserName, Password } = this.state.fields;
return (
<form>
<div>
<label htmlFor="UserName">UserName</label>
<input type="text"
id='UserName'
name='UserName'
value={UserName}
onChange={this.onChangeField}/>
</div>
<div>
<label htmlFor="Password">Password</label>
<input type="password"
id='Password'
name='Password'
value={Password}
onChange={this.onChangeField}/>
</div>
</form>
);
}
}
Search your problem at:
onChangeField = event => {
let name = event.target.name;
let value = event.target.value;
this.setState(prevState => {
prevState.fields[name] = value;
return {
fields: prevState.fields
};
});
};
Using React Hooks also don't forget to set the initial value.
I was using <input type='datetime-local' value={eventStart} /> and initial eventStart was like
const [eventStart, setEventStart] = useState();
instead
const [eventStart, setEventStart] = useState('');.
The empty string in parentheses is difference.
Also, if you reset form after submit like i do, again you need to set it to empty string, not just to empty parentheses.
This is just my small contribution to this topic, maybe it will help someone.
like this
value={this.state.fields && this.state.fields["name"] || ''}
work for me.
But I set initial state like this:
this.state = {
fields: [],
}
I came across the same warning using react hooks,
Although I had already initialized the initial state before as:-
const [post,setPost] = useState({title:"",body:""})
But later I was overriding a part of the predefined state object on the onChange event handler,
const onChange=(e)=>{
setPost({[e.target.name]:e.target.value})
}
Solution
I solved this by coping first the whole object of the previous state(by using spread operators) then editing on top of it,
const onChange=(e)=>{
setPost({...post,[e.target.name]:e.target.value})
}
Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.
Solution : Check if value is not undefined
React / Formik / Bootstrap / TypeScript
example :
{ values?.purchaseObligation.remainingYear ?
<Input
tag={Field}
name="purchaseObligation.remainingYear"
type="text"
component="input"
/> : null
}
The reason of this problem when input field value is undefined then throw the warning from react. If you create one changeHandler for multiple input field and you want to change state with changeHandler then you need to assign previous value using by spread operator. As like my code here.
constructor(props){
super(props)
this.state = {
user:{
email:'',
password:''
}
}
}
// This handler work for every input field
changeHandler = event=>{
// Dynamically Update State when change input value
this.setState({
user:{
...this.state.user,
[event.target.name]:event.target.value
}
})
}
submitHandler = event=>{
event.preventDefault()
// Your Code Here...
}
render(){
return (
<div className="mt-5">
<form onSubmit={this.submitHandler}>
<input type="text" value={this.state.user.email} name="email" onChage={this.changeHandler} />
<input type="password" value={this.state.user.password} name="password" onChage={this.changeHandler} />
<button type="submit">Login</button>
</form>
</div>
)
}
Multiple Approch can be applied:
Class Based Approch: use local state and define existing field with default value:
constructor(props) {
super(props);
this.state = {
value:''
}
}
<input type='text'
name='firstName'
value={this.state.value}
className="col-12"
onChange={this.onChange}
placeholder='Enter First name' />
Using Hooks React > 16.8 in functional style components:
[value, setValue] = useState('');
<input type='text'
name='firstName'
value={value}
className="col-12"
onChange={this.onChange}
placeholder='Enter First name' />
If Using propTypes and providing Default Value for propTypes in case of HOC component in functional style.
HOC.propTypes = {
value : PropTypes.string
}
HOC.efaultProps = {
value: ''
}
function HOC (){
return (<input type='text'
name='firstName'
value={this.props.value}
className="col-12"
onChange={this.onChange}
placeholder='Enter First name' />)
}
Change this
const [values, setValues] = useState({intialStateValues});
for this
const [values, setValues] = useState(intialStateValues);
I also faced the same issue. The solution in my case was I missed adding 'name' attribute to the element.
<div className="col-12">
<label htmlFor="username" className="form-label">Username</label>
<div className="input-group has-validation">
<span className="input-group-text">#</span>
<input
type="text"
className="form-control"
id="username"
placeholder="Username"
required=""
value={values.username}
onChange={handleChange}
/>
<div className="invalid-feedback">
Your username is required.
</div>
</div>
</div>
After I introduced name = username in the input list of attributes it worked fine.
For functional component:
const SignIn = () => {
const [formData, setFormData] = useState({
email: "",
password: ""
});
const handleChange = (event) => {
const { value, name } = event.target;
setFormData({...formData, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("Signed in");
setFormData({
email: "",
password: ""
});
};
return (
<div className="sign-in-container">
<form onSubmit={handleSubmit}>
<FormInput
name="email"
type="email"
value={formData.email}
handleChange={handleChange}
label="email"
required
/>
<FormInput
name="password"
type="password"
value={formData.password}
handleChange={handleChange}
label="password"
required
/>
<CustomButton type="submit">Sign in</CustomButton>
</form>
</div>
);
};
export default SignIn;
While this might sound crazy, the thing that fixed this issue for me was to add an extra div. A portion of the code as an example:
... [Other code] ...
const [brokerLink, setBrokerLink] = useState('');
... [Other code] ...
return (
... [Other code] ...
<div styleName="advanced-form" style={{ margin: '0 auto', }}>
{/* NOTE: This div */}
<div>
<div styleName="form-field">
<div>Broker Link</div>
<input
type="text"
name="brokerLink"
value={brokerLink}
placeholder=""
onChange={e => setBrokerLink(e.target.value)}
/>
</div>
</div>
</div>
... [Other code] ...
);
... [Other code] ...
Was very strange. Without this extra div, it seems react initially rendered the input element with no value attribute but with an empty style attribute for some reason. You could see that by looking at the html. And this led to the console warning..
What was even weirder was that adding a default value that is not an empty string or doing something like value={brokerLink || ''} would result in the exact same problem..
Another weird thing was I had 30 other elements that were almost exactly the same but did not cause this problem. Only difference was this brokerLink one did not have that outer div..
And moving it to other parts of the code without changing anything removed the warning for some reason..
Probably close to impossible to replicate without my exact code. If this is not a bug in react or something, I don't know what is.
The problem occurs even if you set undefined to the value at a previous rendering that happened even before initializing things properly.
The issue went by replacing
value={value}
with
value={(value==undefined?null:value)}
For me, this was the mistake:
<input onChange={onClickUpdateAnswer} value={answer.text}>
{answer.text}
</input>
As you see, I have passes string into the body of the Input tag,
Fix:
<input onChange={onClickUpdateAnswer} value={answer.text}></input>

Categories

Resources