React: Have to constantly update state to make textarea editable - javascript

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.

Related

Textarea wont allow me to write into it [React]

I'm making a web app with react and in one of my functions I fetch some data from the backend and then I show it to the user by changing the value of the textarea. The problem is that I want the textarea not to be edited when the user presses a button to fetch the data from the backend but then if I click the textarea I want it to erease the data fetched and allow the user to write into itself.
This is how the code looks like in the component that contains the textarea:
<div id="text-div" onClick={this.onWrite}>
<textarea id="text" rows={13} value={this.state.value} onChange={this.handleChange}></textarea></div>
<Button onClick={this.handleRead}>Read</Button>
//functions within the code
onWrite() {
// do stuff
document.getElementById("text").setAttribute("contentEditable", true);
}
async handleRead() {
// fetches data and saves it into this.state.value
document.getElementById("text").setAttribute("contentEditable", false);
}
I've also tried using readOnly but it doesn't work either, when I call handleRead() it doesnt let the user write and shows the data (as expected) but when i call onWrite() (it would set readOnly property to false) it does not let the user write. So I cant revert what handleRead() did.
I would really appreciate some suggestions because I'm kind of a noob in React and this is my first project, sorry if I missed something.
contenteditable is an attribute added to non-editable fields like div to make them editable
You want to use readonly or disabled
I converted your example to vanilla to make it usable here
function onWrite() {
// do stuff
document.getElementById("text").removeAttribute("readonly");
}
async function handleRead() {
document.getElementById("text").value = 'test'
document.getElementById("text").setAttribute("readonly", true)
}
<div id="text-div" onclick="onWrite()">
<textarea id="text" rows="13"></textarea>
</div>
<button onclick="handleRead()">Read</button>
This might be because you have already assigned some value to your input field.
So use defaultValue instead of value
So try writing your code like this
<div id="text-div" onClick={this.onWrite}>
<textarea id="text" rows={13} defaultValue={this.state.value} onChange={this.handleChange}>
</textarea>
</div>
<Button onClick={this.handleRead}>Read</Button>
This should solve your problem.

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)

React/Next.js how to get other Element event target value on button click

I have an input element and a button in React:
<li><input type="text" placeholder="Enter new ID"></input>
<button onClick={(e)=>this.saveKonfigElementHandler()}>Save</button></li>
Now, when I enter some value into the input field, I want to save the value into some array when I click on the button.
Is it somehow possible to get a reference to that input field (e.g. the target.value of the input field) to save it when clicking the button?
Or would I simply have to do it with an onChange event that saves the current input value into some Variable, and when I click the button, I will simply retrieve that value to save it into some array? Maybe that would be a lot simpler.
E.g:
<input type="text" value={this.state.inputFieldText.join('')} onChange={(event) => this.textHandler(event)}></input>
in the textHandler Method, I will save the target value from the input field into a Class Component state variable in the textHandler() method. And when I click the button, I retrieve that state value and can do some manipulation with it?
A modern way to do it, with function components, hooks and a controlled form element, is:
import { useState } from 'react'
function MyComponent({ initialId, onSave }) {
const [newId, setNewId] = useState(initialId)
return (
<li>
<input
type="text"
placeholder="Enter new ID"
onChange={(e) => setNewId(e.target.value)}
/>
<button onClick={() => onSave(newId)}>Save</button>
</li>
)
}
I'd also note that it is considered better accessibility practice to use a label element for describing the purpose of your field, rather than a placeholder. Placeholders are more appropriate for example input.
Is it somehow possible to get a reference to that input field (e.g. the target.value of the input field) to save it when clicking the button?
Yes.
Or would I simply have to do it with an onChange event that saves the current input value into some Variable, and when I click the button, I will simply retrieve that value to save it into some array? Maybe that would be a lot simpler.
That would be a slightly more React way to do it.
Your DOM-only approach is more "uncontrolled" (see these docs for what controlled/uncontrolled means). You'd do it like this:
Change your onClick to pass e to the handler:
onClick={(e)=>this.saveKonfigElementHandler(e)}
In saveKonfigElementHandler, use e.target.previousElementSibling to access the input:
saveKonfigElementHandler(e) {
const { value } = e.target.previousElementSibling;
// Use `value` ...
}
That's fragile, of course; if you change your structure so another element is between the button and the input, or the input is within a container element, etc., it'll break — which is one argument for the controlled approach. You could store a link to the input in a data attribute on the button:
<li><input id="id-input" type="text" placeholder="Enter new ID"/>
<button data-input="#id-input" onClick={(e)=>this.saveKonfigElementHandler(e)}>Save</button></li>
and then use querySelector to get it:
saveKonfigElementHandler(e) {
const { value } = document.querySelector(e.target.getAttribute("data-input"));
// Use `value` ...
}
but the you're having to keep selectors unique, etc.
Which you choose, controlled or uncontrolled, is ultimately up to you.
I'm not sure about your question. Do you want something like this ?
<button data-input="#id-input" onClick={this.saveKonfigElementHandler(value)}>Save</button></li>
saveKonfigElementHandler = (value) => (event) => {}

input field not behaving as expected while making atom plugin?

I'm newbie in making atom plugins. I have to make a field which will take an input. I'm using etch for component management. In the render method, I've made this input field
<input
name={'component'}
type={'text'}
onchange={this.onInputChange}
value={this.componentName}
/>
I've made this onInputChange method which looks like :
onInputChange = (event) => {
this.componentName = event.target.value;
etch.update(this);
}
but I'm getting an event which comes after some time (kinda debounced event), furthermore I'm not able to delete the text from input. What is the right way to make an input.
<div class="native-key-bindings">
<input/>
</div>
Try wrapping it in this way. It will also fix the delete issue.

React controlled component input value with empty string

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.

Categories