Related
class EditLocation extends Component {
constructor(props) {
super();
this.state = {
LocationId: '',
locationOptions: [],
}
this.baseState = this.state;
this.findLocationById = this.findLocationById.bind(this);
}
findLocationById = (locationId) => {
let locationOptions = [];
if (locationId <= 0) {
setTimeout(()=>{
this.setState(this.baseState);
locationOptions.push(
<CustomInput
type="checkbox"
id={value.LocationTypeId}
key={value.LocationTypeId}
value={value.LocationTypeId}
defaultChecked={false}
label={`${value.LocationTypeName}`}
className="mb-0"
onChange={this.handleCheckbox.bind(this)}
/>
)
this.setState({locationOptions:locationOptions})
},200)
else {
setTimeout(() => {
let location = this.props.store.selectedLocation;
this.props.store.LocationTypes.forEach((value)=>{
if(location.LocationTypes ?
location.LocationTypes.includes(value.LocationTypeId): false)
{
locationOptions.push(
<CustomInput
type="checkbox"
id={value.LocationTypeId}
key={value.LocationTypeId}
value={value.LocationTypeId}
defaultChecked={true}
label={`${value.LocationTypeName}`}
className="mb-0"
onChange={this.handleCheckbox.bind(this)}
/>
)
}
else
{
locationOptions.push(
<CustomInput
type="checkbox"
id={value.LocationTypeId}
key={value.LocationTypeId}
value={value.LocationTypeId}
defaultChecked={false}
label={`${value.LocationTypeName}`}
className="mb-0"
onChange={this.handleCheckbox.bind(this)}
/>
)
}
})
this.setState({
LocationId: location.LocationId,
locationOptions: locationOptions,
})
render(){
return (
<div>
<Modal>
<Form>
<FormGroup>
<input
value={this.state.LocationId}
type="text"
name="Location"
id="Location"
/>
</FormGroup>
<FormGroup>
{console.log(this.state.locationOptions)} // showing updated state value
{this.state.locationOptions} // showing previous state.locationOptions value
</FormGroup>
</Form>
</Modal>
</div>
)
}
}
console.log() inside the render is updating the value by my checks on customInput are not updating. I need to either reopen the modal or reload the whole program to see updates.
Any solution and resources would be helpful as I am stuck at it for hours and can't seem to figure the issue. and store is mobx store if that helps
You using setState in a wrong way.
setState() enqueues changes to the component state and tells React
that this component and its children need to be re-rendered with the
updated state. This is the primary method you use to update the user
interface in response to event handlers and server responses.
React docs
So it asynchronous and you can't guarantee when update happens.
Using setTimeout is a bad manners in React.
Storing whole components in state, e.g. locationOptions isn't good idea either.
Better to move input to separate component, as i see only defaultChecked different.
Better to use Hooks, easier to think about this in React way, it's require some time and effort to figure out how to write declarative instead of imperative code.
Can refactor a litle
// sync local state and props is a pain, better to avoid it
//constructor(props) {
//super(props);
//const location = this.props.store.selectedLocation
// this.state = {
//LocationId: location.LocationId,
//}
//}
const location = this.props.store.selectedLocation;
render() {
return (
<div>
<Modal>
<Form>
<FormGroup>
<input
value={this.props.store.selectedLocation}
type="text"
name="Location"
id="Location"
/>
</FormGroup>
<FormGroup>
{this.props.store.cationTypes.map((value) => (
<CustomInput
type="checkbox"
id={value.LocationTypeId}
key={value.LocationTypeId}
value={value.LocationTypeId}
defaultChecked={location.LocationTypes.includes(value.LocationTypeId)}
label={`${value.LocationTypeName}`}
className="mb-0"
onChange={this.handleCheckbox.bind(this)}
/>}
))
</FormGroup>
</Form>
</Modal>
</div>
);
}
So I am trying to render a form to start a new darts game conditionally depending on the game type selected. The form has a local state and "knows" which game is selected. So when selecting game "X01" I need a variant, inCondition and outCondition Dropdown whereas the game "Cricket" just needs one additional dropdown for variant (other values than x01 variants). I started to design a game form which looks like this:
gameform.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import SelectInputMultiple from '../common/SelectInputMultiple';
import SelectInput from '../common/SelectInput';
import { games, x01Variants, conditions, cricketVariants } from './assets';
export default class GameForm extends Component {
constructor(props) {
super(props);
this.players = props;
this.handleMultipleChange = this.handleMultipleChange.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
state = {
selectedPlayers: [],
game: 'x01',
x01variant: '501',
inCondition: 'straight',
outCondition: 'double',
cricketVariant: 'cutthroat',
errors: {}
};
formIsValid() {
const _errors = {};
if (this.state.selectedPlayers.length === 0)
_errors.selectedPlayers = 'You need to select at least one player';
this.setState({
errors: _errors
});
return Object.keys(_errors).length === 0;
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleMultipleChange = e => {
let _selectedPlayers = [...e.target.options]
.filter(o => o.selected)
.map(o => o.value);
this.setState(prevState => ({
selectedPlayers: { ...prevState.selectedPlayers, _selectedPlayers }
}));
};
handleSubmit = e => {
e.preventDefault();
if (!this.formIsValid()) return;
let _game = {
selectedPlayers: this.state.selectedPlayers,
game: this.state.game,
x01Variant: this.state.x01variant,
inCondition: this.state.inCondition,
outCondition: this.state.outCondition,
cricketVariant: this.state.cricketVariant
};
this.props.onSubmit(_game);
};
render() {
return (
<form onSubmit={this.handleSubmit}>
<SelectInputMultiple
id="players"
label="Players"
name="players"
onChange={this.handleMultipleChange}
options={this.props.players}
error={this.state.errors.selectedPlayers}
/>
<SelectInput
id="game"
label="Game Type"
name="game"
onChange={this.handleChange}
options={games}
value={this.state.game}
error={this.state.errors.game}
/>
<SelectInput
id="x01Variant"
label="X01 Variants"
name="x01Variant"
onChange={this.handleChange}
options={x01Variants}
value={this.state.x01variant}
error={this.state.errors.x01Variants}
/>
<SelectInput
id="inCondition"
label="In Condition"
name="inCondition"
onChange={this.handleChange}
options={conditions}
value={this.state.inCondition}
error={this.state.errors.condition}
/>
<SelectInput
id="outCondition"
label="Out Condition"
name="outCondition"
onChange={this.handleChange}
options={conditions}
value={this.state.outCondition}
error={this.state.errors.condition}
/>
<SelectInput
id="cricketVariant"
label="Variant"
name="cricketVariant"
onChange={this.handleChange}
options={cricketVariants}
value={this.state.cricketVariant}
error={this.state.errors.cricketVariant}
/>
<input type="submit" value="Start Game" className="btn btn-primary" />
</form>
);
}
}
GameForm.propTypes = {
onSubmit: PropTypes.func.isRequired,
players: PropTypes.array
};
So my goal is to just show the corresponding fields linked to the game type. How can I do that depending on this.state.game?
Thanks in advance for any hint!
You could just conditionally render the variant select input when the game mode is set to "Cricket"
{this.state.game === 'Cricket' && (
<SelectInput />
)}
The reason you can write it as such is because in JavaScript the && operator basically returns the first value if it is falsy and the second value if the first value is truthy. i.e.
a && b will return 'a' if 'a' is falsy, and it will return 'b' if 'a' is truthy.
So in this case when this.state.game === 'Cricket' then the JSX code will be returned hence rendering the form input.
One additional tip!
If you want to render one of two JSX elements based on a condition, you can just use a ternary expression!
{this.state.game === 'Cricket' ? (
// Renders when game mode is 'Cricket'
<Cricket />
) : (
// Renders when game mode is NOT 'Cricket'
<SomeOtherGame />
)}
So in this case you need a generic and simple solution, so in your state since you have only limited number of games you can have a state variable as shown below.
state = {selectBoxEnabled : {xo1: ['xo1variant', 'inCondition', 'outCondition'], cricket: ['variant'], 'split-score': ['split-score_select']} }
So the above selectBoxEnabled field has the same key as in your dropdown field
Game Select Box : xo1, cricket, split-score has option values
So when the user choose an option you will be getting the key as xo1 Assume
Now you need to find the fields of xo1 game
const findFields = (currentGameKey) => {
let selectBoxKeys = selectBoxEnabled[Object.keys(selectBoxEnabled).find(key => key === currentGameKey)]
this.setState(selectBoxKeys)
}
Now you know which are the fields to be shown since you are using same component SelectInput , you can do a simple array.map and render the fields from the config.
I hope this will give a better understanding of the problem
I was doing my project and in my project, and I faced such a situation
enter image description here
if a user selects one of the there options as an option
if the option is one of the three except they select the form lookalike this
enter image description here
else if they select DDP the form will be changed by adding a new input field
enter image description here
I handle it using a ternary operator based on the input user selected and it works for me
if it's helpful the code snippet will be here
{
ddp !== undefined ? (
incotermData == ddp ? (
<Controls.Input
name="exchangeRate"
label="Exchange Rate"
onChange={(e) => setExchangeRate(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.*
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>
I have a react based form with more than 10 fields in it, I have states for those ten form fields (controlled component).
Most of these input fields are of type text only but other types fields will be added later.
Problem is that I need to write 10 change handlers for them to set state correctly and then add method bindings for each of them in constructor.
I am quite new to react and may be not aware about correct
methodologies and techniques.
Please guide me to how to improve my current code structure and avoid writing boiler plates and repetitive error prone code.
My current Registration component is like below -
export default class Register extends Component {
constructor(props){
super(props);
this.state = {
regName : '',
regAdd1 : '',
regAdd2 : '',
regState : '',
regZipCode : '',
regCity : '',
regPhone : ''
};
// add bindings .... ugh..
this.changeRegAdd1 = this.changeRegAdd1.bind(this);
this.changeRegAdd2 = this.changeRegAdd2.bind(this);
//Similary binding for other handlers...
}
// add individual field change handlers ... ugh...
changeRegName(e) {
this.setState({regName:e.target.value});
}
changeRegAdd1(e) {
this.setState({regAdd1:e.target.value});
}
changeRegAdd2(e) {
this.setState({regAdd2:e.target.value});
}
changeRegState(e) {
this.setState({regState:e.target.value});
}
// Similary for other change handler ....
handleSubmit(e) {
e.preventDefault();
// validate then do other stuff
}
render(){
let registrationComp = (
<div className="row">
<div className="col-md-12">
<h3>Registration Form</h3>
<fieldset>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regName">Name</label>
<input type="text" placeholder="Name"
onChange={this.changeregName} value = {this.state.regName} className="form-control" required autofocus/>
</div>
</div>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regAdd1">Address Line1</label>
<input
type = "text"
placeholder = "Address Line1"
onChange = {this.changeregAdd1}
value = {this.state.regAdd1}
className = "form-control"
required
autofocus
/>
<input
type = "text"
placeholder = "Address Line2"
onChange = {this.changeregAdd2}
value = {this.state.regAdd2}
className = "form-control"
required
autofocus
/>
</div>
</div>
<div className="form-group">
<div className="col-xs-6">
<label htmlFor="regState">State</label>
<input
type = "text"
placeholder = "State"
onChange = {this.changeregState}
value = {this.state.regState}
className = "form-control"
required
autofocus
/>
</div>
<div className="col-xs-6">
<label htmlFor="regZipCode">Zip Code</label>
<input
type = "text"
placeholder = "Zip Code"
onChange = {this.changeregZipCode}
value = {this.state.regZipCode}
className = "form-control"
required
autofocus
/>
</div>
</div>
<div className="form-group">
<div className="col-xs-12">
<label htmlFor="regCity">City</label>
<input
type = "text"
placeholder = "City"
title = "City"
onChange = {this.changeregCity}
value = {this.state.regCity}
className = "form-control"
required
autofocus
/>
</div>
</div>
{/* other form fields */}
</fieldset>
</div>
</div>
);
return registrationComp;
}
}
There can be other lots of ways to do it which I may not be aware.
But I prefer to do change handling in a common method for certain type of common fields such as <input type of text />
This is a sample input field -
<input
onChange = {this.onChange}
value = {this.state.firstName}
type = "text"
name = {"firstName"}
/>
I keep both state's field name and input's "name" attribute same.
After that I write a common change handler for all such fields.
Need to write just one line in change handler.
onChange(e) {
this.setState({[e.target.name]: e.target.value});
}
I am using es6's dynamic property setting from string as property name
{ ["propName] : propValue }.
To avoid writing manual binding for methods in react component you can follow below two approaches -
Use es6 arrow functions
A hack ( I don't know whether this approach is fast or slow but It works :) )
create a method like this in your component.
_bind(...methods) {
methods.forEach( (method) => this[method] = this[method].bind(this) );
}
use _bind to bind your methods.
constructor(props){
super(props);
this.state = {
regName : '',
regAdd1 : '',
regAdd2 : '',
regState : '',
regZipCode : '',
regCity : '',
regPhone : ''
};
// add bindings .... ugh..
//this.changeRegAdd1 = this.changeRegAdd1.bind(this);
//this.changeRegAdd2 = this.changeRegAdd2.bind(this);
//Similary binding for other handlers...
this._bind(
'changeRegName',
'changeReg1' , 'changeRegAdd2'
// and so on.
);
}
EDIT :
Adding validation -
Write a method which can iterate over state keys and check if state is empty.
If any of the input field is empty collect the detail about that input and mark that required.
set a state to indicate that form has errors. Specific error details can be found in state's error object.
validateInput() {
let errors = {};
Object.keys(this.state)
.forEach((stateKey) => {
isEmpty(this.state[stateKey]) ? (errors[stateKey] = `*required` ) : null;
});
return {
errors,
isValid : isEmptyObj(errors)
};
}
isFormValid() {
const { errors, isValid } = this.validateInput();
if (!isValid) {
this.setState({ errors});
}
return isValid;
}
onSubmit(e) {
e.preventDefault();
this.setState({errors : {}});
if (this.isFormValid()) {
// Perform form submission
}
}
I have used to utility method calld isEmpty, isEmptyObj. They just check if object is null or undefined or field is empty.
I hope this helps.
You could create a higher order component to handle a lot of that for you. Redux Form has a pretty good pattern you could model it after if you wanted.
You would essentially end up with something like the following (I have not tested this at all, but it should work pretty well):
export class Field extends Component {
handleChange = (event) => this.props.onChange(this.props.name, event.target.value)
render() {
const InputComponent = this.props.component
const value = this.props.value || ''
return (
<InputComponent
{...this.props}
onChange={this.handleChange}
value={value}
/>
}
}
export default function createForm(WrappedComponent) {
class Form extends Component {
constructor() {
super()
this.state = this.props.initialValues || {}
this.handleChange = this.handleChange.bind(this)
}
handleChange(name, value) {
this.setState({
[name]: value,
})
}
render() {
return (
<WrappedComponent
{...this.state}
{...this.props}
// pass anything you want to add here
onChange={this.handleChange}
values={this.state}
/>
)
}
}
return Form
}
Then you can augment this one component as necessary (add focus, blur, submit handlers, etc). You would use it something like this:
import createForm, { Field } from './createForm'
// simplified Registration component
export class Registration extends Component {
render() {
return (
<form>
<Field
component="input"
name="name"
onChange={this.props.onChange}
type="text"
value={this.props.values.name}
/>
</form>
)
}
}
export default createForm(Registration)
You could go crazy and get into context so you don't have to manually pass values and function around, but I would stay away at least until you're more familiar with React.
Also, if you don't want to manually bind functions, you could use the class properties transform if you're using babel. Then the Form component would just look like the following:
class Form extends Component {
state = this.props.initialValues || {}
handleChange = (name, value) => this.setState({
[name]: value,
})
render() {
return (
<WrappedComponent
{...this.state}
{...this.props}
onChange={this.handleChange}
values={this.state}
/>
)
}
}
Take a look at how it's done in NeoForm:
data state is directly mapped to form fields
one onChange handler for the entire form
per-field (for example onBlur) and form (for example onSubmit) validation
plain object and Immutable state helpers
easy integration with Redux or any other state management solution
It uses context internally and really small and modular source code is easy to follow.
I am just writing to text input and in onChange event I call setState, so React re-renders my UI. The problem is that the text input always loses focus, so I need to focus it again for each letter :D.
var EditorContainer = React.createClass({
componentDidMount: function () {
$(this.getDOMNode()).slimScroll({height: this.props.height, distance: '4px', size: '8px'});
},
componentDidUpdate: function () {
console.log("zde");
$(this.getDOMNode()).slimScroll({destroy: true}).slimScroll({height: 'auto', distance: '4px', size: '8px'});
},
changeSelectedComponentName: function (e) {
//this.props.editor.selectedComponent.name = $(e.target).val();
this.props.editor.forceUpdate();
},
render: function () {
var style = {
height: this.props.height + 'px'
};
return (
<div className="container" style={style}>
<div className="row">
<div className="col-xs-6">
{this.props.selected ? <h3>{this.props.selected.name}</h3> : ''}
{this.props.selected ? <input type="text" value={this.props.selected.name} onChange={this.changeSelectedComponentName} /> : ''}
</div>
<div className="col-xs-6">
<ComponentTree editor={this.props.editor} components={this.props.components}/>
</div>
</div>
</div>
);
}
});
Without seeing the rest of your code, this is a guess.
When you create a EditorContainer, specify a unique key for the component:
<EditorContainer key="editor1"/>
When a re-rendering occurs, if the same key is seen, this will tell React don't clobber and regenerate the view, instead reuse. Then the focused item should retain focus.
I keep coming back here again and again and always find the solution to my elsewhere at the end.
So, I'll document it here because I know I will forget this again!
The reason input was losing focus in my case was due to the fact that I was re-rendering the input on state change.
Buggy Code:
import React from 'react';
import styled from 'styled-components';
class SuperAwesomeComp extends React.Component {
state = {
email: ''
};
updateEmail = e => {
e.preventDefault();
this.setState({ email: e.target.value });
};
render() {
const Container = styled.div``;
const Input = styled.input``;
return (
<Container>
<Input
type="text"
placeholder="Gimme your email!"
onChange={this.updateEmail}
value={this.state.email}
/>
</Container>
)
}
}
So, the problem is that I always start coding everything at one place to quickly test and later break it all into separate modules.
But, here this strategy backfires because updating the state on input change triggers render function and the focus is lost.
Fix is simple, do the modularization from the beginning, in other words, "Move the Input component out of render function"
Fixed Code
import React from 'react';
import styled from 'styled-components';
const Container = styled.div``;
const Input = styled.input``;
class SuperAwesomeComp extends React.Component {
state = {
email: ''
};
updateEmail = e => {
e.preventDefault();
this.setState({ email: e.target.value });
};
render() {
return (
<Container>
<Input
type="text"
placeholder="Gimme your email!"
onChange={this.updateEmail}
value={this.state.email}
/>
</Container>
)
}
}
Ref. to the solution: https://github.com/styled-components/styled-components/issues/540#issuecomment-283664947
If it's a problem within a react router <Route/> use the render prop instead of component.
<Route path="/user" render={() => <UserPage/>} />
The loss of focus happens because the component prop uses React.createElement each time instead of just re-rendering the changes.
Details here: https://reacttraining.com/react-router/web/api/Route/component
I had the same symptoms with hooks. Yet my problem was defining a component inside the parent.
Wrong:
const Parent =() => {
const Child = () => <p>Child!</p>
return <Child />
}
Right:
const Child = () => <p>Child!</p>
const Parent = () => <Child />
My answer is similar to what #z5h said.
In my case, I used Math.random() to generate a unique key for the component.
I thought the key is only used for triggering a rerender for that particular component rather than re-rendering all the components in that array (I return an array of components in my code). I didn't know it is used for restoring the state after rerendering.
Removing that did the job for me.
Applying the autoFocus attribute to the input element can perform as a workaround in situations where there's only one input that needs to be focused. In that case a key attribute would be unnecessary because it's just one element and furthermore you wouldn't have to worry about breaking the input element into its own component to avoid losing focus on re-render of main component.
What I did was just change the value prop to defaultValue and second change was onChange event to onBlur.
I got the same behavior.
The problem in my code was that i created a nested Array of jsx elements like this:
const example = [
[
<input value={'Test 1'}/>,
<div>Test 2</div>,
<div>Test 3</div>,
]
]
...
render = () => {
return <div>{ example }</div>
}
Every element in this nested Array re-renders each time I updated the parent element. And so the inputs lose there "ref" prop every time
I fixed the Problem with transform the inner array to a react component
(a function with a render function)
const example = [
<myComponentArray />
]
...
render = () => {
return <div>{ example }</div>
}
EDIT:
The same issue appears when i build a nested React.Fragment
const SomeComponent = (props) => (
<React.Fragment>
<label ... />
<input ... />
</React.Fragment>
);
const ParentComponent = (props) => (
<React.Fragment>
<SomeComponent ... />
<div />
</React.Fragment>
);
I solved the same issue deleting the key attribute in the input and his parent elements
// Before
<input
className='invoice_table-input invoice_table-input-sm'
type='number'
key={ Math.random }
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
/>
// After
<input
className='invoice_table-input invoice_table-input-sm'
type='number'
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
/>
The answers supplied didn't help me, here was what I did but I had a unique situation.
To clean up the code I tend to use this format until I'm ready to pull the component into another file.
render(){
const MyInput = () => {
return <input onChange={(e)=>this.setState({text: e.target.value}) />
}
return(
<div>
<MyInput />
</div>
)
But this caused it to lose focus, when I put the code directly in the div it worked.
return(
<div>
<input onChange={(e)=>this.setState({text: e.target.value}) />
</div>
)
I don't know why this is, this is the only issue I've had with writing it this way and I do it in most files I have, but if anyone does a similar thing this is why it loses focus.
If the input field is inside another element (i.e., a container element like <div key={"bart"}...><input key={"lisa"}...> ... </input></div>-- the ellipses here indicating omitted code), there must be a unique and constant key on the container element (as well as on the input field). Elsewise, React renders up a brand new container element when child's state is updated rather than merely re-rendering the old container. Logically, only the child element should be updated, but...
I had this problem while trying to write a component that took a bunch of address information. The working code looks like this
// import react, components
import React, { Component } from 'react'
// import various functions
import uuid from "uuid";
// import styles
import "../styles/signUp.css";
export default class Address extends Component {
constructor(props) {
super(props);
this.state = {
address1: "",
address2: "",
address1Key: uuid.v4(),
address2Key: uuid.v4(),
address1HolderKey: uuid.v4(),
address2HolderKey: uuid.v4(),
// omitting state information for additional address fields for brevity
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
event.preventDefault();
this.setState({ [`${event.target.id}`]: event.target.value })
}
render() {
return (
<fieldset>
<div className="labelAndField" key={this.state.address1HolderKey} >
<label className="labelStyle" for="address1">{"Address"}</label>
<input className="inputStyle"
id="address1"
name="address1"
type="text"
label="address1"
placeholder=""
value={this.state.address1}
onChange={this.handleChange}
key={this.state.address1Key} ></input >
</div>
<div className="labelAndField" key={this.state.address2HolderKey} >
<label className="labelStyle" for="address2">{"Address (Cont.)"}</label>
<input className="inputStyle"
id="address2"
name="address2"
type="text"
label="address2"
placeholder=""
key={this.state.address2Key} ></input >
</div>
{/* omitting rest of address fields for brevity */}
</fieldset>
)
}
}
Sharp-eyed readers will note that <fieldset> is a containing element, yet it doesn't require a key. The same holds for <> and <React.Fragment> or even <div> Why? Maybe only the immediate container needs a key. I dunno. As math textbooks say, the explanation is left to the reader as an exercise.
I had this issue and the problem turned out to be that I was using a functional component and linking up with a parent component's state. If I switched to using a class component, the problem went away. Hopefully there is a way around this when using functional components as it's a lot more convenient for simple item renderers et al.
I just ran into this issue and came here for help. Check your CSS! The input field cannot have user-select: none; or it won't work on an iPad.
The core reason is: When React re-render, your previous DOM ref will be invalid. It mean react has change the DOM tree, and you this.refs.input.focus won't work, because the input here doesn't exist anymore.
For me, this was being caused by the search input box being rendered in the same component (called UserList) as the list of search results. So whenever the search results changed, the whole UserList component rerendered, including the input box.
My solution was to create a whole new component called UserListSearch which is separate from UserList. I did not need to set keys on the input fields in UserListSearch for this to work. The render function of my UsersContainer now looks like this:
class UserContainer extends React.Component {
render() {
return (
<div>
<Route
exact
path={this.props.match.url}
render={() => (
<div>
<UserListSearch
handleSearchChange={this.handleSearchChange}
searchTerm={this.state.searchTerm}
/>
<UserList
isLoading={this.state.isLoading}
users={this.props.users}
user={this.state.user}
handleNewUserClick={this.handleNewUserClick}
/>
</div>
)}
/>
</div>
)
}
}
Hopefully this helps someone too.
I switched value prop to defaultValue. That works for me.
...
// before
<input value={myVar} />
// after
<input defaultValue={myVar} />
My problem was that I named my key dynamically with a value of the item, in my case "name" so the key was key={${item.name}-${index}}. So when I wanted to change the input with item.name as the value, they key would also change and therefore react would not recognize that element
included the next code in tag input:
ref={(input) => {
if (input) {
input.focus();
}
}}
Before:
<input
defaultValue={email}
className="form-control"
type="email"
id="email"
name="email"
placeholder={"mail#mail.com"}
maxLength="15"
onChange={(e) => validEmail(e.target.value)}
/>
After:
<input
ref={(input) => {
if (input) {
input.focus();
}
}}
defaultValue={email}
className="form-control"
type="email"
id="email"
name="email"
placeholder={"mail#mail.com"}
maxLength="15"
onChange={(e) => validEmail(e.target.value)}
/>
I had a similar issue, this is fixed it.
const component = () => {
return <input onChange={({target})=>{
setValue(target.vlaue)
}
} />
}
const ThisComponentKeptRefreshingContainer = () => {
return(
<component />
)
}
const ThisContainerDidNot= () => {
return(
<> {component()} </>
)
}
As the code illustrate calling the component child like an element gave that re-rendering effect, however, calling it like a function did not.
hope it helps someone
I had the same problem with an html table in which I have input text lines in a column. inside a loop I read a json object and I create rows in particular I have a column with inputtext.
http://reactkungfu.com/2015/09/react-js-loses-input-focus-on-typing/
I managed to solve it in the following way
import { InputTextComponent } from './InputTextComponent';
//import my inputTextComponent
...
var trElementList = (function (list, tableComponent) {
var trList = [],
trElement = undefined,
trElementCreator = trElementCreator,
employeeElement = undefined;
// iterating through employee list and
// creating row for each employee
for (var x = 0; x < list.length; x++) {
employeeElement = list[x];
var trNomeImpatto = React.createElement('tr', null, <td rowSpan="4"><strong>{employeeElement['NomeTipologiaImpatto'].toUpperCase()}</strong></td>);
trList.push(trNomeImpatto);
trList.push(trElementCreator(employeeElement, 0, x));
trList.push(trElementCreator(employeeElement, 1, x));
trList.push(trElementCreator(employeeElement, 2, x));
} // end of for
return trList; // returns row list
function trElementCreator(obj, field, index) {
var tdList = [],
tdElement = undefined;
//my input text
var inputTextarea = <InputTextComponent
idImpatto={obj['TipologiaImpattoId']}//index
value={obj[columns[field].nota]}//initial value of the input I read from my json data source
noteType={columns[field].nota}
impattiComposite={tableComponent.state.impattiComposite}
//updateImpactCompositeNote={tableComponent.updateImpactCompositeNote}
/>
tdElement = React.createElement('td', { style: null }, inputTextarea);
tdList.push(tdElement);
var trComponent = createClass({
render: function () {
return React.createElement('tr', null, tdList);
}
});
return React.createElement(trComponent);
} // end of trElementCreator
});
...
//my tableComponent
var tableComponent = createClass({
// initial component states will be here
// initialize values
getInitialState: function () {
return {
impattiComposite: [],
serviceId: window.sessionStorage.getItem('serviceId'),
serviceName: window.sessionStorage.getItem('serviceName'),
form_data: [],
successCreation: null,
};
},
//read a json data soure of the web api url
componentDidMount: function () {
this.serverRequest =
$.ajax({
url: Url,
type: 'GET',
contentType: 'application/json',
data: JSON.stringify({ id: this.state.serviceId }),
cache: false,
success: function (response) {
this.setState({ impattiComposite: response.data });
}.bind(this),
error: function (xhr, resp, text) {
// show error to console
console.error('Error', xhr, resp, text)
alert(xhr, resp, text);
}
});
},
render: function () {
...
React.createElement('table', {style:null}, React.createElement('tbody', null,trElementList(this.state.impattiComposite, this),))
...
}
//my input text
var inputTextarea = <InputTextComponent
idImpatto={obj['TipologiaImpattoId']}//index
value={obj[columns[field].nota]}//initial value of the input I read //from my json data source
noteType={columns[field].nota}
impattiComposite={tableComponent.state.impattiComposite}//impattiComposite = my json data source
/>//end my input text
tdElement = React.createElement('td', { style: null }, inputTextarea);
tdList.push(tdElement);//add a component
//./InputTextComponent.js
import React from 'react';
export class InputTextComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
idImpatto: props.idImpatto,
value: props.value,
noteType: props.noteType,
_impattiComposite: props.impattiComposite,
};
this.updateNote = this.updateNote.bind(this);
}
//Update a inpute text with new value insert of the user
updateNote(event) {
this.setState({ value: event.target.value });//update a state of the local componet inputText
var impattiComposite = this.state._impattiComposite;
var index = this.state.idImpatto - 1;
var impatto = impattiComposite[index];
impatto[this.state.noteType] = event.target.value;
this.setState({ _impattiComposite: impattiComposite });//update of the state of the father component (tableComponet)
}
render() {
return (
<input
className="Form-input"
type='text'
value={this.state.value}
onChange={this.updateNote}>
</input>
);
}
}
Simple solution in my case:
<input ref={ref => ref && ref.focus()}
onFocus={(e)=>e.currentTarget.setSelectionRange(e.currentTarget.value.length, e.currentTarget.value.length)}
/>
ref triggers focus, and that triggers onFocus to calculate the end and set the cursor accordingly.
The issue in my case was that the key prop values I was setting on the InputContainer component and the input fields themselves were generated using Math.random(). The non-constant nature of the values made it hard for track to be kept of the input field being edited.
For me I had a text area inside a portal. This text area was loosing focus. My buggy portal implementation was like this:
export const Modal = ({children, onClose}: modelProps) => {
const modalDOM = document.getElementById("modal");
const divRef = useRef(document.createElement('div'));
useEffect(()=>{
const ref = divRef.current;
modalDOM?.appendChild(ref);
return ()=>{
modalDOM?.removeChild(ref);
}
});
const close = (e: React.MouseEvent) => {
e.stopPropagation();
onClose();
};
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation()
}
return (
createPortal(
<div className="modal" onClick={close}>
<div className="modal__close-modal" onClick={close}>x</div>
{children}
</div>,
divRef.current)
)
}
const Parent = ({content: string}: ParentProps) => {
const [content, setContent] = useState<string>(content);
const onChangeFile = (e: React.MouseEvent) => {
setContent(e.currentTarget.value);
}
return (
<Modal>
<textarea
value={content}
onChange={onChangeFile}>
</textarea>
</Modal>
)
}
Turned out following implementation worked correctly, here I am directly attaching modal component to the DOM element.
export const Modal = ({children, onClose}: modelProps) => {
const modalDOM = document.getElementById("modal");
const close = (e: React.MouseEvent) => {
e.stopPropagation();
onClose();
};
return (
createPortal(
<div className="modal" onClick={close}>
<div className="modal__close-modal" onClick={close}>x</div>
{children}
</div>,
modalDOM || document.body)
)
}
Turns out I was binding this to the component which was causing it to rerender.
I figured I'd post it here in case anyone else had this issue.
I had to change
<Field
label="Post Content"
name="text"
component={this.renderField.bind(this)}
/>
To
<Field
label="Post Content"
name="text"
component={this.renderField}
/>
Simple fix since in my case, I didn't actually need this in renderField, but hopefully me posting this will help someone else.
Changing text in the input of some control can cause parent control rerendering in some cases (according to binding to props).
In this case focus will be lost. Editing should not has effect to parent container in DOM.