How to keep cursor position in a react input element - javascript

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);
}}
/>

Related

Cant get a text box state to refresh with user input, keeps going to default state despite using setState

I have a React file which displays a list of city data as a component. there is an input textbox above it which needs to accept user input. i am using state to display an initial string in the textbox, but i cannot get onChange to successfully use a function to setState. troubleshooting it with console.log i can see that when i attempt to change the state the function i am pointing to with onChange does work and changes one letter, but then the state snaps back to its default value. the problem seems to be with setState not saving the change and reverting back to the initial state after any changes are made. the text box content appears to not change at all, thought console.log shows a one letter change but then reverts back to the original state.
how do i update state? i want the user to be able to punch a number in and then compare it with the list.
import React, {Component} from 'react';
import Table from './Table';
import cities from './Cities';
class App extends Component {
state = {
userInput: "Your City Population"
}
popChanger = (event) => {
this.setState( {userInput: event.target.value} );
//console.log(event.target.value);
}
yourCity = (
<div>
<input
type='text'
onChange={this.popChanger}
value={this.state.userInput}
/>
</div>
)
render() {
return (
<div className = "App">
{this.yourCity}
<Table characterData = {cities} />
</div>
);
}
}
export default App;
setState() is saving your changes, just not in the right place,
popChanger() is an arrow function and updates the state of the App component,
yourCity has it's own this so it doesn't know about the App state.
you can either cahnge yourCity to an arrow function that returns the html you want like
class TodoApp extends React.Component {
state = {
a: ''
};
YourCity = () => (
<div>
<input type="text" onChange={this.handleChange} value={this.state.a} />
</div>
}
handleChange = e => this.setState({a : e.target.value})
render() {
return (
<div>
<this.YourCity />
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
Or, create yourCity component outside and pass the handleChange as a prop :
const YourCity = props => (
<div>
<input type="text" onChange={props.handleChange} value={props.value} />
</div>
)
class TodoApp extends React.Component {
state = {
a: ''
};
handleChange = e => this.setState({a : e.target.value})
render() {
return (
<div>
<YourCity handleChange={this.handleChange} value={this.state.a}/>
</div>
)
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"))
The state is updating but you can't see that because this.yourCity doesn't re-render
popChanger = (event) => {
this.setState( {userInput: event.target.value} );
console.log(event.target.value);
}
yourCity(){
return <div>
<input
type='text'
onChange={this.popChanger}
value={this.state.userInput}
/>
</div>
}
render() {
return (
<div className = "App">
{this.yourCity()}
</div>
);
}
}

onKeyUp not working in React where onChange did

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.

how to add new input field after click plus icon in React Js

I'd like to add a new input everytime the plus icon is clicked but instead it always adds it to the end. I want it to be added next to the item that was clicked.
Here is the React code that I've used.
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add</i>
<i>Remove</i>
</div>
</div>
);
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [Input]
};
}
addInput = index => {
this.setState(prevState => ({
choices: update(prevState.choices, { $splice: [[index, 0, Input]] })
}));
};
render() {
return (
<div>
{this.state.choices.map((Element, index) => {
return (
<Element
key={index}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
<div id="app"></div>
<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>
I must admit this get me stuck for a while but there was a problem with how react deals with key props. When you use an index as a key it doesn't work. But if you make sure inputs will always be assigned the same key even when the list changes it will work as expected:
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add </i>
<i>Remove</i>
</div>
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [],
nrOfElements: 0
};
}
addInput = index => {
this.setState(prevState => {
const choicesCopy = [...prevState.choices];
choicesCopy.splice(index, 0, `input_${prevState.nrOfElements}`);
return {
choices: choicesCopy,
nrOfElements: prevState.nrOfElements + 1
};
});
};
componentDidMount() {
this.addInput(0);
}
render() {
return (
<div>
{this.state.choices.map((name, index) => {
return (
<Input
key={name}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
Some reference from the docs:
Keys should be given to the elements inside the array to give the
elements a stable identity...
...We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state.

How to pass onChange to a stateless component

I have been playing around with reactjs, and building some basic forms all works good but then it became complicated when i tried to break into smaller compoments
i have a simple component that has a form in it called XMLParser, and it has 2 smaller components inside called XMLInput and XMLResult.
XMLResult is pretty straight forward it just passes the value to as the props, but couldnt figure out what is the best approact to use XMLInput i couldnt get the binding to work with the child component, thanks for any pointers.
function EppResult(props) {
const resultXML = props.xmlData;
return <div style={styles.child}>
<textarea style={styles.outputBox} name="resultXML" value={resultXML} readOnly/>
</div>;
}
class EppInput extends Component{
render(){
return <textarea
style={styles.inputBox}
placeholder="Enter XML here!"
name="xmlData"
value={this.props.value}
onChange={this.props.handleInputChange}
/>;
}
}
class XMLParser extends Component {
constructor(props) {
super(props);
this.state = {xmlData : ""};
this.state = {resultXML : ""};
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log("do submit");
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div style={styles.container}>
<form onSubmit={this.handleSubmit}>
<div style={styles.container}>
<div style={styles.child}>
<XMLInput value={this.state.xmlData} onChange={this.handleInputChange} />
</div>
<div style={styles.child}>
<div style={styles.formContainer}>
<button style={styles.formElement}>Run!</button>
</div>
</div>
<XMLResult xmlData={this.state.resultXML}/>
</div>
</form>
</div>
);
}
}
I'm seeing a number of problems:
EppInput is the sub-component name, but XMLInput is used in the main component
You pass an onChange prop, but refer to it as
this.props.handleInputChange -- it should be
this.props.onChange
Just a note--I don't know where the styles object is, but I'm going
to assume it's available to all the components
I haven't tested this, but here's a basic cleanup, with some alterations to see some other ways of doing things:
// stateless functional component
const XMLResult = ({ xmlData }) => (
<div style={styles.child}>
<textarea
style={styles.outputBox}
name="resultXML"
value={xmlData}
readOnly
/>
</div>
);
// stateless functional component
// props are passed directly to the child element using the spread operator
const XMLInput = (props) => (
<textarea
{...props}
style={styles.inputBox}
placeholder="Enter XML here!"
name="xmlData"
/>
);
class XMLParser extends Component {
constructor(props) {
super(props);
this.state = { xmlData: "", resultXML: "" };
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
console.log("do submit");
}
handleInputChange(event) {
const target = event.target;
const value = target.type === "checkbox" ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<div style={styles.container}>
<form onSubmit={this.handleSubmit}>
<div style={styles.container}>
<div style={styles.child}>
<XMLInput
value={this.state.xmlData}
onChange={this.handleInputChange}
/>
</div>
<div style={styles.child}>
<div style={styles.formContainer}>
<button style={styles.formElement}>Run!</button>
</div>
</div>
<XMLResult xmlData={this.state.resultXML} />
</div>
</form>
</div>
);
}
}

How to get values from input types using this.refs in reactjs?

Not able to get values of input type using this.refs...
how to get that values from input type
export class BusinessDetailsForm extends Component {
submitForm(data) {
console.log(this.refs.googleInput.value)
}
}
reder() {
return(
<form onSubmit={this.submitForm}>
<Field type="text"
name="location"
component={GoogleAutoComplete}
id="addressSearchBoxField"
ref="googleInput"
/>
</form>
)
}
}
You should avoid ref="googleInput" as it is now considered legacy. You should instead declare
ref={(googleInput) => { this.googleInput = googleInput }}
Inside of your handler, you can use this.googleInput to reference the element.
Then inside of your submitForm function, you can obtain the text value with
this.googleInput._getText()
String refs are legacy
https://facebook.github.io/react/docs/refs-and-the-dom.html
If you worked with React before, you might be familiar with an older API where the ref attribute is a string, like "textInput", and the DOM node is accessed as this.refs.textInput. We advise against it because string refs have some issues, are considered legacy, and are likely to be removed in one of the future releases. If you're currently using this.refs.textInput to access refs, we recommend the callback pattern instead.
Edit
From React 16.3, the format for creating refs are:
class Component extends React.Component
{
constructor()
{
this.googleInput = React.createRef();
}
render()
{
return
(
<div ref={this.googleInput}>
{/* Details */}
</div>
);
}
}
using ref={ inputRef => this.input = inputRef } is considered legacy now. In React 16.3 onwards, you can use the code below,
class MyForm extends React.Component {
constructor(props) {
//...
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
EDIT: thanks for the comment #stormwild
In case any one is wondering how to implement ref with hooks :
// Import
import React, { useRef } from 'react';
const Component = () => {
// Create Refs
const exampleInput = useRef();
const handleSubmit = (e) => {
e.preventDefault();
const inputTest = exampleInput.current.value;
}
return(
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" ref={exampleInput} />
</label>
<input type="submit" value="Submit" />
</form>
}
getValue: function() {
return this.refs.googleInput.value;
}
I think the more idiomatic way is to use state instead of refs, although it's a little more code in this case since you only have a single input.
export class BusinessDetailsForm extends Component {
constructor(props) {
super(props);
this.state = { googleInput: '' };
this.defaultValue = 'someValue';
this.handleChange = this.handleChange.bind(this);
this.submitForm = this.submitForm.bind(this);
}
handleChange(e) {
const { field, value } = e.target;
this.setState({ [field]: value });
}
submitForm() {
console.log(this.state.googleInput);
}
render() {
return (
<Formsy.Form onSubmit={this.submitForm} id="form_validation">
<Field type="text"
name="googleInput"
onChange={this.handleChange}
component={GoogleAutoComplete}
floatingLabelText="location"
hintText="location"
id="addressSearchBoxField"
defaultValue={this.defaultValue}
onSelectPlace={this.handlePlaceChanged}
validate={[ required ]}
/>
</Formsy.Form>
);
}
}
See https://facebook.github.io/react/docs/forms.html#controlled-components.
Using RN 0.57.8 when tried this.googleInput._getText(), It resulted in error _getText is not a function so i printed this.googleInput in console and found that _getText() is a function inside _root
this.googleInput._root._getText()
this.googleInput._root._lastNativeText - This will return the last state not the current state please be careful while using it.
In 2018 you should write in constructor this:
In constructor of class you should add something like
this.input = React.createRef()
Examples here:
https://reactjs.org/docs/uncontrolled-components.html
I tried the answer above (https://stackoverflow.com/a/52269988/1978448) and found it only worked for me when I put the refs in the state, but not when I just made them properties of the component.
Constructor:
this.state.refs={
fieldName1: React.createRef(),
fieldName2: React.createRef()
};
and in my handleSubmit I create a payload object to post to my server like this:
var payload = {
fieldName1: this.state.refs.fieldName1.current.value,
fieldName2: this.state.refs.fieldName2.current.value,
}
The react docu explains it very well: https://reactjs.org/docs/refs-and-the-dom.html
this is considered legacy:
yourHandleMethod() {
this.googleInput.click();
};
yourRenderCode(){
ref={(googleInput) => { this.googleInput = googleInput }}
};
whereas, this is considered the way to go:
constructor(props){
this.googleInput = React.createRef();
};
yourHandleMethod() {
this.googleInput.current.click();
};
yourRenderCode(){
<yourHTMLElement
ref={this.googleInput}
/>
};
From React 16.2, you can use: React.createRef
See more: https://reactjs.org/docs/refs-and-the-dom.html
1. using ref={ inputRef => this.input = inputRef }
Exam:
import React, { Component } from 'react';
class Search extends Component {
constructor(props) {
super(props);
this.name = React.createRef();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.onSearch(`name=${this.name.value}`);
}
render() {
return (
<div>
<input
className="form-control name"
ref={ n => this.name = n }
type="text"
/>
<button className="btn btn-warning" onClick={ this.handleClick }>Search</button>
</div>
);
}
}
export default Search;
ref={ n => this.name = n } Use Callback Refs -> see
Or:
2. this.name.current.focusTextInput()
class Search extends Component {
constructor(props) {
super(props);
this.name = React.createRef();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.onSearch(`name=${this.name.current.value}`);
}
render() {
return (
<div>
<input
className="form-control name"
ref={this.name}
type="text"
/>
<button className="btn btn-warning" onClick={ this.handleClick }>Search</button>
</div>
);
}
}
export default Search;
Hope it will help you.

Categories