React controlled component input value with empty string - javascript

I wrote a demo below for the test React controlled component input feature. But It seems there is a bug.
class TestComponent extends React.Component{
constructor() {
super();
this.state = {value: 'beef'};
this.handleValueChange = this.handleValueChange.bind(this);
}
handleValueChange(e) {
this.setState({value: e.target.value});
}
render() {
return <div>
<div><input type='text' value={'hello world'} onChange={this.handleValueChange}/></div>
<div><input type='text' value={''} onChange={this.handleValueChange}/></div>
<div><input type='text' value={this.state.value} onChange={this.handleValueChange}/></div>
<div><input type='text' value={null} onChange={this.handleValueChange}/></div>
<div><input type='text' value={undefined} onChange={this.handleValueChange}/></div>
<hr/>
<div><input type='text' defaultValue={this.state.value} onChange={this.handleValueChange}/></div>
<p>{this.state.value}</p>
</div>
}
}
ReactDOM.render(
<TestComponent />,
document.body
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
The first one input with specifying string value property. when I type something, call handleValueChange function, and the result is hello world + your type thing's first character.
The second one input with empty string value property. when I type something, it calls handleValueChange function, but finally, it always gives me one character.
It's weird.
updated!
I add a input with defaultValue, compare with value={this.state.value}, my head is mess up..

As mentioned in a comment below your question: remove value={''} since this will empty the input everytime it's rendered.
From the react docs, the correct way to do it is
return <div>
<input type='text' value={this.state.value} onChange={this.handleValueChange}/>
<p>{this.state.value}</p>
</div>
This way whenever you type something in the input-area, you'll update the value set in state.
If you want to render the component with a value set in state, you could use:
getInitialState() {
return { value: 'Your default value'}
}
Or, as already suggested, use defaultValue.
Read more here: https://facebook.github.io/react/docs/forms.html
Update:
According to your updated question, I think you'll have to understand what setting a value during render function actually does. Whenever you set a value during render function, you'll "lock" the input field to be that specific value. Meaning the user input will have no effect on the rendered element. From the docs: "User input will have no effect on the rendered element because React has declared the value".
To solve this problem, you'll have to set the value to be something you can change dynamically, in your case that will be value or this.state.value. Like you've done in your third example:
<input type='text' value={this.state.value} onChange={this.handleValueChange}/>
This way React accept the value provided by the user input and will then update the value of the component thereafter.
I still think the docs specifies this pretty clearly, and I think you should read the provided documentation in my original answer.
Update 2
To clearify the part with controlled and uncontrolled components little bit:
A controlled component is a component that has a value property assigned, and will reflect the value from the user input (the value prop).
While an uncontrolled component does not have any value property assigned, and will NOT reflect the value from the user input (since it does not provide any value prop). But if you want to instantiate an uncontrolled component with a value, you should use defaultValue.
In your case (since you try to use a CONTROLLED component) this means that you should NOT use defaultValue, and stick with a correct implementation of a controlled component. That is an implementation using value={this.state.value}.
Again I recommend reading the docs provided. It's actually not that difficult if you manage to understand the docs.
Hope this clearifies some of you problems! :)

use defaultValue instead of value
render() {
return <div>
<div><input type='text' defaultValue={'hello world'} onChange={this.handleValueChange}/></div>
<div><input type='text' defaultValue={''} onChange={this.handleValueChange}/></div>
<div><input type='text' defaultValue={this.state.value} onChange={this.handleValueChange}/></div>
<div><input type='text' defaultValue={null} onChange={this.handleValueChange}/></div>
<div><input type='text' defaultValue={undefined} onChange={this.handleValueChange}/></div>
<p>{this.state.value}</p>
</div>
}
}

The only correct controlled component here is:
<input type='text' value={this.state.value} onChange={this.handleValueChange}/>
The others are not controlled. However, because the others are calling setState and changing the value too, they are affecting the third input.
See what happens:
First input will call setState with e.target.value = hello world + first character you typed. So that will be the new value of the third input
Second input will call setState with only one character since the value of that input is always ''. So one character will be the new value of the third input
Fourth input the same as the second one
Last input, since the value is set to undefined... That is the same as if you don't define any value there, so the fourth input will not be controlled but every time you type it's copying it's actual value to the third input
Conclusion:
To get a controlled input, always set the value to something you control it's change (state or props).
Be careful when changing the same part of the state using different inputs... It becomes really complicated to reason about.

Related

getting error React does not recognize the `handleChange` prop on a DOM element

I am trying to make one login form in react .but I am getting this error
React does not recognize the handleChange prop on a DOM element. If you intentionally want it to appear in the DOM as a custom attribute, spell it as lowercase handlechange instead.
input value is also not setting input field when I type in input field it is not updating the state why ?
here is my code
https://codesandbox.io/s/quirky-clarke-qbkjw
<form noValidate>
<TextBox
label="Username"
name="username"
type="text"
helperText="Assistive text"
handleChange={handleChange}
handleMouseDownPassword={handleMouseDownPassword}
value={userId}
/>
<TextBox
label="Password"
name="password"
type="password"
helperText="Assistive text"
value={password}
showPassword={showPassword}
handleChange={handleChange}
handleClickShowPassword={handleClickShowPassword}
handleMouseDownPassword={handleMouseDownPassword}
/>
<div className="button-group">
<button>LOGIN</button>
</div>
</form>
In the TextBox component you're passing all the props from the parent via {...props}. Considering that TextField itself doesn't have handleChange property, I assume it passes it down to the underlying input DOM element, which doesn't recognise that prop.
What you can do is to extract the props used inside TextBox and collect the rest using the rest argument, so you don't end up passing unnecessary props down:
export default function TextBox({handleChange, handleClickShowPassword, handleMouseDownPassword, value, ...props}) {
Another option is to remove {...props} from the TextField component and explicitly pass all the necessary props.
Updated Sandbox
React doesn't like uppercase letters in the prop names. Instead of passing "handleChange", pass "handlechange". You will get a similar error for "handleMouseDownPassword".
Regarding the input issue, I don't think you've provided enough context. But you have to have a handleChange method to update the state each time the field is changed.
I changed the handleChange() function, you were only setting the VALUE state, you need to set the state userId when you write on the first input, and the password when you write to the second input
At b.js add props name={stateName} so the handleChange() can know which input is controlling
Check Demo for more:
https://codesandbox.io/s/gracious-heisenberg-z4q4z
(other answeres explained why you are getting that error in console ...props)

ReactJS: Save input value on blur and not on every key stroke

I have created a React View, say MyView, which has 2 text inputs whose initial values will be passed by parent read from a DB.
I also want the changed values to be saved back to DB. So, the view is also passed a callback function for the same.
Consider that DB save operation is heavy and you should not do it very frequently. So, I decided to listen to onBlur events instead of onChange events on the input boxes as onChange is invoked on every key stroke.
First Approach:
class MyView extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<input type="url" value={this.props.values.A}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
<input type="url" value={this.props.values.B}
onBlur={(evt)=>{this.props.saveValue('B', evt.target.value)}} />
<button type="button" onClick={this.props.resetValues}>Reset</button>
</div>
);
}
}
However, this does not work as React enforces a controlled input (with value attribute) always to be accompanied by an onChange listener.
Second Approach:
So, I tried to make these inputs as uncontrolled. That is, instead of value attribute, used defaultValue.
<input type="url" defaultValue={this.props.values.A}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
But this also did not work as on reset/clear button click, although the view was made to re-render but defaultValue does not update once view is created.
Third Approach:
So, I finally added an onChange listener but as no-op.
<input type="url" value={this.props.values.A}
onChange={()=>{console.log('do nothing')}
onBlur={(evt)=>{this.props.saveValue('A', evt.target.value)}} />
Again, this did not work as the view re-renders after calling onChange and since value is not reflected in props yet, value seems to reset back to initial on every key stroke.
Fourth Approach:
Last I tried was to maintain a state in component and read value from state and on every onChange save the value back to state. This worked to most extent but whenever there were external changes to props and the view was re-rendered, state did not update. So, I added a getDerivedStateFromProps function to view:
static getDerivedStateFromProps(props, state) {
return props.values;
}
Now, this again did not work. Reason being that this function is invoked even if I temporarily save values to state and the state was reset to initial values in props.
Can some ReactJS expert help me with my use-case?
You will still need onChange to help you set the states of both url input. onBlur is only used to trigger saving, it's 2 different events for different purposes.
Since your A & B values are passed down from parent component. MyView's parent component should pass down this.state.values and the functions to set the state.
Refer to this snippet if everything is in single component. You should be able move handleChange function up to its parent component.
class App extends React.Component {
state = {
values: {
A: '',
B: ''
}
}
handleChange = e => {
this.setState({
values: {
...this.state.values,
[e.target.name]: e.target.value
})
}
handleBlur = e => {
if (e.target.name === 'A') {
alert(`Saving A: ${this.state.values.A}`)
}
if (e.target.name === 'B') {
alert(`Saving B: ${this.state.values.B}`)
}
}
render() {
return (
<div>
<label>Value A</label>
<input
type="url"
name="A"
value={this.state.values.B}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
<label>Value B</label>
<input
type="url"
name="B"
value={this.state.values.A}
onChange={this.handleChange}
onBlur={this.handleBlur}
/>
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
</div>
EDIT: Your fourth approach should work with the following:
static getDerivedStateFromProps(props, state) {
return { values: props.values }
}
constructor(props) {
super(props)
this.state = {
values: props.values
}
}
so basically the this.state.values is the final source of truth. When user types something, you setState in this component and change it. But if props.values changes (from external source), getDerivedStateFromProps will update the values state.
Going by the comments on Liren Yeo's solution, I would handle the props-state reconciliation on componentDidUpdate, where you get both the old state and props. This way you can determine how this.props was updated and act accordingly. When the value in props does not match state nor oldProps, the update is external and you should override the unsaved changes in the state.
The code should look something like this
componentDidUpdate(prevProps) {
if (this.props.values !== prevProps.values && this.props.values !== this.state.values) {
this.setState({values:this.props.values});
}
}
If you go this route, you can also leave the input uncontrolled and update its value through a reference. This solves some unreliability with controlled inputs, like for example, a type='number' returning undefined as its value when you type a decimal comma. You still need to store the value onChange but only save it onBlur and handling the state-prop-dom reconciliation in componentDidUpdate
So with the idea that onChange works, I would recommend you to take a look at this:
https://schier.co/blog/2014/12/08/wait-for-user-to-stop-typing-using-javascript.html
Navigate to the heading: Wait for Typing to Stop
Hope it can somehow lead you to what you want to achieve.

React: Have to constantly update state to make textarea editable

In my component, I render this textarea:
<textarea id="descript" rows="8" value={this.state.description} />
and I was surprised to find that I couldn't type in it. After some experimentation, I found that I was typing it in, but react was just reseting the value every time I typed a character to the state variable. So, I've gotten it to let me type by adding this onChange:
<textarea id="descript" rows="8" value={this.state.description}
onChange={() => {this.textChanged();}} />
and this is the handler:
textChanged(event) {
var newDesc = document.getElementById('descript').value;
this.setState({'description':newDesc});
}
This works, but it's an incredibly expensive way to have React not do anything. Is there a better/right way to approach this that will just let me edit the textarea? For example, with the input fields in the form, I just use defaultValue instead, but I need this one to be multiple lines.
The way you are trying is okay. Just need a little tweak. Try this:
<textarea
id="descript"
rows="8"
value={this.state.description}
onChange={this.textChanged}
/>
textChanged(event) {
this.setState({
description: event.target.value
});
}
And don't forget to bind the function in your constructor like this:
this.textChanged= this.textChanged.bind(this);
As you are using it as a controlled component so the state will always be reflected in the textarea's value.
In React, this is called a controlled-component. Since you're setting your <textarea>'s value to this.state.description, the <textarea> will always reflect this, making the React state the source of truth. As a result you must also handle changes. You may be falling victim to premature optimization, this is generally very fast, and is a best practice. In React, your component rendering should be predicable based on your state and props.
As Arslan mentioned, however, since your handler receives event just the same as a normal onChange handler, a simpler way to do this is simply to set your state to event.target.value rather than grabbing a reference to your <textarea> and getting it's value.
Is there a reason why the textarea contents need to be in the state?
You can use another variable (or a property passed into the component) instead:
<textarea id="descript" rows="8" value={description} />
or
<textarea id="descript" rows="8" value={this.props.description} />
Then instead of an onChange handler, you read the updated value of the textarea when you are ready to use it.
You are using the as a controlled component, where the value it holds will always be this.state.description. You are right that when used this way you will need supply an onChange function but you do not need to set an id and use the document API.
You can read more about it here: https://reactjs.org/docs/forms.html#controlled-components
You do not need to use it as a controlled component. If you don't set the value prop you will be able to type as is. Then when you wish to capture that value you can read the value from the textarea element either by ref or by id.

Input form - change the running order of onChange and isDisabled

I have implemented in React a webpage with 3 input fields, each one with the properties of
onChange={this.handleChange} and disabled={this.isDisabled()}
The desired behavior is that when an input field contains 2 digits, the focus will be moved to the next input field.
As long as the field doesn't contain 2 digits, the fields next to must be disabled.
What actually happens, when I type the second digit in the first field, it runs the handleChange function, that function checks whether the field contains 2 digits, find out that yes, and moves the focus to the next input field.
But the next field is disabled! (because the isDisabled function didn't run yet!)
So the cursor doesn't move..
I want to change the order of the happenings, or any other way to solve it.
Do you have any suggestions?
The problem is that this.isDisabled() runs immediately in render but this.handleChange runs on click and most possibly doesn't change the state thus no rerender.
You sholdn't run function on next input, you should pass true or false to its disabled prop. Just make handleChange update the state which defines which fields are disabled. And pass that state to your inputs accordingly.
I had faced the same issue a few days back. My approach was however using react states and focusing the input by its id, that was fetched from state;
So first we make a input - id map for our convenience. And use document.getElementById(this.state.active).focus() function. We change our state via our change handler.
render() {
this.setInputFocus();
return (
<div className="App">
<input id="1" onChange={this.onChange} />
<input id="2" onChange={this.onChange} />
<input id="3" onChange={this.onChange} />
</div>
);
}
setInputFocus = () => {
if (document.getElementById(this.state.active)) {
document.getElementById(this.state.active).focus();
}
};
onChange = e => {
if (e.target.value.length === 2) {
this.setState({ active: this.state.active + 1 });
}
};
Here is a full code that somewhat solves the issue

Text Box coming disabled in React JS using Type Script

I have used TSX file for loading text box in React JS as below :
<input type={'text'} value={employees.length > 0 ? employees[0].name : ""} id=
{'Name'} label={'Name'} name={'Name'} htmlFor={'Name'} />
Now when I load this file then we cant write any thing in text box.
So anyone can please help me?
You cannot write anything because you have created a controlled component which means that the value of the input will always be whatever the value prop evaluates to - in this case employees[0].name or "".
With some minor adjustments you can make this work, either by making it a controlled component with an event listener which updates the value, or by making it an uncontrolled component.
In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.
More info here and here.
Option 1 - Controlled component (recommended)
To make a controlled component, you need to add the onChange event listener and have that update the variable that you pass into value. It's not clear from your question what employees is; if it's a state or prop variable or something else.
Assuming it's a state variable, you can do:
handleChange = (e) => {
let arr = this.state.employees.slice();
arr[0].name = e.target.value;
this.setState({employees: arr});
}
render() {
let {employees} = this.state;
return (
<input type={'text'} onChange={this.handleChange} value={employees.length > 0 ? employees[0].name : ""} id={'Name'} label={'Name'} name={'Name'} htmlFor={'Name'} />
);
}
Option 2 - Uncontrolled component
You only need a slight modification to make your input an uncontrolled component. Simply replace value with defaultValue:
<input type={'text'} defaultValue={employees.length > 0 ? employees[0].name : ""} id={'Name'} label={'Name'} name={'Name'} htmlFor={'Name'} />
As a side note, you don't need to wrap string literals in brackets. So instead of <input type={'text'} ... you can just do <input type='text' ..., if you prefer.

Categories