onKeyUp not working in React where onChange did - javascript

I'm doing a React coding challenge that requires a value to be updated onKeyUp. I initially set it to update onChange but the tests require onKeyUp so I tried to change it to that, but my fields are no longer updating and I can't type anything into the textarea.
class MarkdownApp extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp(event) {
this.setState({ value: event.target.value })
}
render() {
return (
<form>
<label>
Enter your markdown here:
<br />
<textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
<br />
</label>
<label>
Your markup will be previewed here:
<p id='preview'>{marked(this.state.value)}</p>
</label>
</form>
);
}
}
ReactDOM.render(
<MarkdownApp />,
document.getElementById('root')
);
Like I said, this worked fine when it was onChange and my function was handleChange, but since I switched it I can't type anything.

I would just remove the value attribute from the textarea. Because if you put the value attribute to it then the user won't be able to change it interactively. The value will always stay fixed(unless you explicitly change the value in your code). You don't need to control that with React--the DOM will hold onto the value for you.
The only change I've made below is to remove value={this.state.value} from the textarea element:
import React from 'react';
import ReactDOM from 'react-dom';
class MarkdownApp extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp(event) {
this.setState({ value: event.target.value })
}
render() {
return (
<form>
<label>
Enter your markdown here:
<br />
<textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
<br />
</label>
<label>
Your markup will be previewed here:
<p id='preview'>{this.state.value}</p>
</label>
</form>
);
}
}
ReactDOM.render(
<MarkdownApp />,
document.getElementById('root')
);

Since the event happens before the actual value of the textbox is changed, the result of event.target.value is an empty string. Setting the state with the empty string, clears the textbox.
You need to get the pressed key value from the event, and add it to the existing state.value.
Note: I've removed marked from the demo
class MarkdownApp extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
this.handleKeyUp = this.handleKeyUp.bind(this);
}
handleKeyUp(event) {
const keyValue = event.key;
this.setState(({ value }) => ({
value: value + keyValue
}))
}
render() {
return (
<form>
<label>
Enter your markdown here:
<br />
<textarea value={this.state.value} onKeyUp={this.handleKeyUp} id='editor' />
<br />
</label>
<label>
Your markup will be previewed here:
<p id='preview'>{this.state.value}</p>
</label>
</form>
);
}
}
ReactDOM.render(
<MarkdownApp />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

You could make the textarea uncontrolled by not giving it the value and simply storing the value in state from a ref instead.
Example (CodeSandbox)
class MarkdownApp extends React.Component {
ref = null;
state = {
value: ""
};
handleKeyUp = event => {
this.setState({ value: this.ref.value });
};
render() {
return (
<form>
<label>
Enter your markdown here:
<br />
<textarea
onKeyUp={this.handleKeyUp}
ref={ref => (this.ref = ref)}
id="editor"
/>
<br />
</label>
<label>
Your markup will be previewed here:
<p id="preview">{marked(this.state.value)}</p>
</label>
</form>
);
}
}

The issue is you have a two way binding with the state = to the value in your textbox. OnChange would update your state after a change is made and the events are done firing. Onkeyup returns the value onkeyup and since you mapped that to your state it will stay as nothing. Remove the value prop and it should work.

Related

How to update the state using react this.setState by directly passing in object

I am simply trying to get the text from the input field for which handler function is attached to a button and then just trying to store in input value into the state object.
But the state object doesn't store that value
class EditTextArea extends React.Component {
constructor() {
super();
this.state = {
message: "",
};
this.handleButton = this.handleButton.bind(this);
}
handleButton(e) {
const text = e.target.previousElementSibling.value;
this.setState({ message: text });
console.log(this.state);
}
render() {
return (
<div>
<form>
<input type="text" name="customText" />
<button type="button" onClick={this.handleButton}>
Send
</button>
</form>
</div>
);
}
}
ReactDOM.render(<EditTextArea />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
Nothing in your render is using this.state.message, so you're not going to see anything happen. (Note that if you're wondering about the console.log of state, see this question's answers; the update is asynchronous.)
If you actually use message, you see that it works:
class EditTextArea extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "",
};
this.handleButton = this.handleButton.bind(this);
}
handleButton(e) {
const text = e.target.previousElementSibling.value;
this.setState({ message: text });
}
render() {
return (
<div>
<div>
Message is: {this.state.message}
</div>
<form>
<input type="text" name="customText" />
<button type="button" onClick={this.handleButton}>
Send
</button>
</form>
</div>
);
}
}
ReactDOM.render(<EditTextArea />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
Also note that you need to accept a parameter in your constructor and pass it to super(); see above. That wasn't the problem, but it still still incorrect.
That said, I wouldn't do it that way. If you want an uncontrolled input, use a ref to access its value so you don't have to do the DOM traversal, which is easily broken with a small change to render:
class EditTextArea extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "",
};
this.handleButton = this.handleButton.bind(this);
this.inputRef = React.createRef(null);
}
handleButton() {
const text = this.inputRef.current.value;
this.setState({ message: text });
}
render() {
return (
<div>
<div>
Message is: {this.state.message}
</div>
<form>
<input type="text" name="customText" ref={this.inputRef} />
<button type="button" onClick={this.handleButton}>
Send
</button>
</form>
</div>
);
}
}
ReactDOM.render(<EditTextArea />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
Or you might consider a controlled component.
More about controlled vs. uncontrolled components hrere. More about refs here.
I run your script so initially, your message state taking an empty state just add a text and click a couple of times so Your message state is updating the second time.
So if you need to dynamic change on input so I suggest you right an input handler and call it into the input change function and handle it separately.
onInputchange(e) {
this.setState({
[e.target.name]: e.target.value
});
}
I tried your code and it actually worked. But the problem is you cant change state and get changed state in same function, it reads the previus state. Just try to click button 2 times, you will see it works
In react states are updated asynchronously. To check your updated state you can check it by logging in render or in react developer tools.
According to you code you can check code given below
class EditTextArea extends React.Component {
constructor() {
super();
this.state = {
message: "",
};
this.handleButton = this.handleButton.bind(this);
}
handleButton(e) {
const text = e.target.previousElementSibling.value;
this.setState({ message: text });
console.log(this.state);//It will show you old value
}
render() {
console.log("check your updated state",this.state)
return (
<div>
<form>
<input type="text" name="customText" />
<button type="button" onClick={this.handleButton}>
Send
</button>
</form>
</div>
);
}
}

How to keep cursor position in a react input element

The cursor keeps going to the end. How to keep the cursor position when editing from the the middle of the string?
Code that I am using is:
const rootElement = document.getElementById('root');
class MyFancyForm extends React.Component {
constructor(props) {
super(props);
this.state = {myValue: ""};
}
handleCommaSeparatedChange = event => {
const {value} = event.target;
this.setState({myValue: value});
};
render() {
return(
<form >
<div>
<label>
Cursor position looser
<br />
<input onChange={this.handleCommaSeparatedChange} value={this.state.myValue} />
</label>
</div>
</form>
)
}
}
const element = <MyFancyForm />;
ReactDOM.render(element, rootElement);
Any idea how could I achieve it?
just change value into defaultValue - it worked both in codepen and codesandbox for me
class MyFancyForm extends React.Component {
constructor(props) {
super(props);
this.state = {myValue: ""};
}
handleCommaSeparatedChange = event => {
const {value} = event.target;
this.setState({myValue: value});
};
render() {
return(
<form >
<div>
<label>
Cursor position looser
<br />
<input onChange={this.handleCommaSeparatedChange} defaultValue={this.state.myValue} />
</label>
</div>
</form>
)
}
}
ReactDOM.render(
<MyFancyForm />,
document.getElementById('root')
);
I know it's an old post but this might help someone.
I found this snippet in one github issue and helped me to get around this problem.
onChange={(event) => {
event.persist()
const caretStart = event.target.selectionStart;
const caretEnd = event.target.selectionEnd;
// update the state and reset the caret
this.updateState();
event.target.setSelectionRange(caretStart, caretEnd);
}}
Quote from:
https://github.com/facebook/react/issues/955#issuecomment-469344232
I solved this by creating a TextInput component that wraps <input type="text"> and proxying the value in internal state.
function TextInput({ value, onChange }) {
// Create a proxy value in internal state to prevent the caret from jumping to the end every time the value updates
const [currentValue, setCurrentValue] = useState<string>(value);
useEffect(() => {
setCurrentValue(value);
}, [value]);
return (<input
type="text"
value={currentValue}
onChange={(e) => {
setCurrentValue(e.target.value);
onChange(e.target.value);
}}
/>);
}
Then I use it in a parent component like so. It works well so far.
<TextInput
value={textValue}
onChange={(e) => {
setTextValue(e);
}}
/>

Coordinated input in all text boxes

I have an exercise and I do not understand how to do it.
Build a control with 5 text boxes that the input in all text boxes is correlated. Changing input in one box changes the input in the rest of the box.
class App extends Component {
render() {
return (
<div>
<MultiInput />
</div>
);
}
}
function SameInput(props) {
return <input type="text" name="sameInput"/>
}
class MultiInput extends React.Component {
constructor(props) {
super(props);
this.state = { value: "ho" };
}
onChange = (e) => {
this.setState({ value: e.target.value });
}
render() {
return <div>
<SameInput value={this.state.value} onChange={this.onChange}></SameInput><br />
<SameInput value={this.state.value} onChange={this.onChange}></SameInput><br />
<SameInput value={this.state.value} onChange={this.onChange}></SameInput><br />
</div>
}
}
export default App;
You are passing onChange event handler function and value to SameInput functional component but you are not actually handling them in input text field in SameInput function of input element.
Try with below change it would work
function SameInput(props) {
return <input type="text" name="sameInput" onChange={props.onChange} value={props.value}/>
}

How to dynamically set State from Form input

I have 2 React parent/child components. The Child Component has a button that adds +1 to the previous state of the Parent Component, and a Form that triggers a handleChange function for the onChange event.
The Problem
From the Form input, I want to trigger a function that sets the State to the previous State, + the input in the Form.
For example, if I write 50 in input and hit submit I want the new state be 100
Here is a codesandbox: https://codesandbox.io/s/30mz2vvyo1
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 50
}
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState((prevState) => {
return { value: prevState.value + 1 }
});
}
handleSubmit(event) {
event.preventDefault();
}
render() {
return (
<div>
<Child value={this.state.value} handleChange={this.handleChange} handleSubmit={this.handleSubmit} />
</div>
)
}
}
class Child extends React.Component {
render() {
return (
<div>
<button onClick={this.props.handleChange}>Count + 1</button>
<div>{this.props.value}</div>
<form onSubmit={this.props.handleSubmit}>
<label>
Name:
<input type="text" onChange={this.props.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
</div>
)
}
}
The problem you are facing can be mitigated by;
You need to have two different variables in state. value, can hold your value. You also need to hold the current value of the input, let's call it inputNumber.
You need to provide an onClick function to your button. In said function, set your state in the following fashion;
Code:
this.setState({
value: this.state.value + this.state.inputNumber,
})
After doing these things, it should work as expected.
I have updated your codesandbox, you can take a look at it here.

How to avoid duplicate event listener in react?

I have a form in react with many input components. I do not like that I have to write a new onChange handler method for every input component that I build. So I want to know how can I stop repeated code.
<Input
label={"Blog Name"}
hint={"e.g. 'The Blog'"}
type={"text"}
value={this.state.name}
onChange={this.handleInputChange.bind(this, "name")}
/>
<Input
label={"Blog Description"}
hint={"e.g. 'The Blog Description'"}
type={"text"}
value={this.state.desc}
onChange={this.handleInputChange.bind(this, "desc")}
/>
So instead of writing a new function I am reusing the same function and passing an extra value. Is this the right way to do it? How do other experienced people solve this problem.
If you want your parent component to maintain the state with the value of each input field present in 'Input' child components, then you can achieve this with a single change handler in the following way:
handleChange(id, value) {
this.setState({
[id]: value
});
}
where the id and value are obtained from the Input component.
Here is a demo: http://codepen.io/PiotrBerebecki/pen/rrJXjK and the full code:
class App extends React.Component {
constructor() {
super();
this.state = {
input1: null,
input2: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(id, value) {
this.setState({
[id]: value
});
}
render() {
return (
<div>
<Input id="input1"
changeHandler={this.handleChange} />
<Input id="input2"
changeHandler={this.handleChange} />
<p>See input1 in parent: {this.state.input1}</p>
<p>See input2 in parent: {this.state.input2}</p>
</div>
);
}
}
class Input extends React.Component {
constructor() {
super();
this.state = {
userInput: null
};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
const enteredText = event.target.valuel
this.setState({
userInput: enteredText
}, this.props.changeHandler(this.props.id, enteredText));
}
render() {
return (
<input type="text"
placeholder="input1 here..."
value={this.state.userInput}
onChange={this.handleChange} />
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
You can try event delegation, just like the traditional ways.
That is, just bind a function to the parent form element, and listens to all the events bubbling up from the children input elments.

Categories