Send cursor to the end of input value in React - javascript

I am dynamically passing a value to my input field when clicking delete (in order to edit the last input entry).
I can see that in Chrome once the input value gets rendered the cursor shows up a the beginning of the word, while in Safari and Firefox goes at the end of the value but the last letter gets deleted.
How can I always see the cursor at the end without deleting the last letter(unless I hit backspace twice)?
tagEvent(e) {
const tag = this.text.value;
const tagGroup = tag.split(" ");
const tiles = this.props.tiles;
const hasTiles = Object.keys(tiles).length > 0;
if(e.keyCode === 32 || e.keyCode === 13){
e.preventDefault();
tagGroup.map(tag => this.props.addTile(tag));
this.tagForm.reset();
}
if(e.keyCode === 8 && hasTiles && tag === '' ) {
this.props.editLastTile();
this.tagForm.reset();
}
}
render() {
return (
<div className="input-wrapper">
<form ref={(input) => this.tagForm = input}>
<input ref={(input) => this.text = input}
type="text"
name="new-item"
placeholder="type and press space"
autoComplete="off"
defaultValue={this.props.value}
onKeyDown={(e) => this.tagEvent(e)} />
</form>
</div>
)
}
Here a Pen with the full code
Thanks a lot for the help!

Another simple solution:
<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.

You can explicitly set cursor position, to do so add this to Input:
componentDidUpdate(prevProps) {
if (prevProps.value !== this.props.value) {
this.text.selectionStart = this.text.value.length;
this.text.selectionEnd = this.text.value.length;
}
}
To prevent removing last character add a e.preventDefault() after if(e.keyCode === 8 && hasTiles && tag === '' ) {
Edited Pen

For those of you coming here trying to use this with react hooks 🙌
A simple texfield component that toggles the type of the input to password/text, this is the typical case where you would like to allow users to see their password by clicking on a button to toggle the type and see the value.
function TextField() {
const [type, setType] = useState('text');
const inputRef = useRef(null);
const onToggle = useCallback(() => {
setType(current => type === 'text' ? 'password' : 'text');
// Setting focus here
inputRef.current.focus();
}, []);
useEffect(() => {
// Moving cursor to the end
inputRef.current.selectionStart = inputRef.current.value.length;
inputRef.current.selectionEnd = inputRef.current.value.length;
}, [type]);
return (
<div>
<input
ref={inputRef}
type={type}
/>
<button onClick={onToggle}>toggle type</button>
</div>
);
}

Related

How to limit a textarea to 4 rows of bullet points?

I was wondering if it is possible to limit a user to only enter 4 lines in a text area. I have tried using maxRows, but that isn't working as I thought. Notice how I have put maxLength to 9999999, as I don't mind how much text is entered on each bullet point, I just want to limit it to a maximum of 4 new line characters/bullet points. If anyone has a solution to how I could accomplish this, that would be great.
<TextField
onKeyUp={handleInput}
inputProps={{
maxLength: 9999999
}}
sx={{ ...fieldCSS, width: '100%', marginTop: '6px' }}
multiline
rows={4}
value={details}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
setDetails(event.target.value);
setCalled({ ...called, detail: true });
}}
error={!canSubmitDetails && called.detail}
helperText={detailsHelperText}
/>
See in the image below, I dont want the user being able to enter that 4th bullet point.
EDIT: after adding the suggested answer with a minor edit but event.stopPropagation(); isnt working
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
const { code, target } = event;
const { value } = target as HTMLInputElement;
if (code === 'Enter') {
if ([...value].filter((word) => word === '\n').length >= DETAIL_MAX_LINES) {
event.stopPropagation();
}
}
};
Cheers,
Has400
You can use the onKeyDown event to check if the user is pressing the enter key. If so, you can check if the number of lines is equal to 4. If so, you can prevent the default behavior of the enter key.
const handleKeyDown = (event: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key === 'Enter') {
const lines = event.currentTarget.value.split('\n');
if (lines.length === 4) {
event.preventDefault();
}
}
};
<TextField
onKeyDown={handleKeyDown}
...
/>
EDIT: It doesn’t look like the enter key method is working, so try using the onChange event to check if the number of lines is greater than 4.
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
if ([...value].filter((word) => word === '\n').length >= 4) {
event.stopPropagation();
}
};
<TextField
onChange={handleChange}
...

Use react to focus input element with caret/text-cursor at end of text using onKeyDown

I have two input fields in my component:
const MyComponent = () => {
const onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'ArrowLeft') {
ref.current?.setSelectionRange(value.length, value.length)
ref.current?.focus()
}
}
const [value, setValue] = useState('1234')
const ref = useRef<HTMLInputElement>(null)
return (
<>
<input
onChange={({target}) => setValue(target.value)}
value={value}
ref={ref}
type={'text'}/>
<input
onKeyDown={onKeyDown}
type={'text'}/>
</>
)
}
When i hit the left arrow-key on the second input, the first input should be focused, and the cursor should be at the end of the input text.
But the cursor is at the wrong place. Why is this not working?
Was the cursor moving one position to the left of the last character?
Interestingly, when using onKeyUp (the release of the key) rather than onKeyDown the issue seems to go away. I've listed that solution followed by a couple other examples with explanations below.
Solution
import { useRef, useState } from "react";
const MyComponent = () => {
const [value, setValue] = useState("1234");
const ref = useRef(null);
const onKeyUp = (event) => {
if (event.key === "ArrowLeft") {
const textInput = ref.current;
const len = value.length;
textInput.setSelectionRange(len, len);
textInput.focus();
}
};
return (
<>
<input
onChange={({ target }) => setValue(target.value)}
value={value}
ref={ref}
type={"text"}
/>
<input onKeyUp={onKeyUp} type={"text"} />
</>
);
};
export default MyComponent;
https://codesandbox.io/s/cursor-end-of-input-onkeyup-x6ekke
Explanation
My guess is that because onKeyUp naturally follows onKeyDown, when we receive the onKeyDown event in React, per your example code, the following sequence occurs (or generally speaking something like this is happening):
Inside onKeyDown...
Our cursor is moved to the very end of the text input.
ref.current?.setSelectionRange(value.length, value.length)
The text input receives focus.
ref.current?.focus()
Then, the release of the left arrow key causes onKeyUp event to run in the DOM (we haven't done anything to handle this in React) while the focus is now on the text input as a result of step 2 above. The default behavior pressing/releasing the left arrow key while the input has focus is to move the cursor one position to the left, placing it one position from the end of the input text.
Other Examples/Solutions
If you stick with the use of onKeyDown, here are a couple other examples.
event.preventDefault()
const onKeyDown = (event) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
const textInput = ref.current;
const len = value.length;
textInput.setSelectionRange(len, len);
textInput.focus();
}
};
setTimeout()
const onKeyDown = (event) => {
if (event.key === "ArrowLeft") {
setTimeout(() => {
const textInput = ref.current;
const len = value.length;
textInput.setSelectionRange(len, len);
textInput.focus();
}, 0);
}
};
https://codesandbox.io/s/cursor-end-of-input-example-h1yrdr
My guess is that these workarounds effectively block the browser from firing the native key down and up events altogether or delay our handler from running until after the native events have fired, respectively.

Enter key for Route another page

I have a input for searchbox. I must make like; Write my words fors search then after i press enter it must need go another page with input value. So i can access that value with query string. So how can i route another page with value of input after i press enter ? Thank you for help! I Just add That codes for catch enter press.
useEffect(() => {
const listener = (event) => {
if (event.code === "Enter" || event.code === "NumpadEnter") {
alert("Enter key was pressed. Run your function.");
event.preventDefault();
}
};
document.addEventListener("keydown", listener);
return () => {
document.removeEventListener("keydown", listener);
};
}, []);
You don't necessarily have to set an event listener, using onKeyDown event handler will also do. Enter key has a code of 13, so we just have to detect that.
Keep your value in a state (here, myValue), detect that you've pressed Enter key (here, using keyPressHandler method), and finally, pass the parameter to your route.
import {useHistory} from "react-router-dom"
function App() {
let history = useHistory();
const [myValue, setMyValue] = useState("");
const handleChange = ({ target: { value } }) => {
setMyValue(value);
};
const keyPressHandler = (e) => {
if (e.which === 13) {
// alert("You pressed enter!");
history.push("/process/" + myValue);
}
};
return (
<div className="App">
<input value={myValue} onKeyDown={keyPressHandler} onChange={handleChange} />
</div>
);
}
UPDATE:
According to MDN Web Docs, e.which is non-standard [Source] and e.keyCode is deprecated [Source], so you should be using e.key instead like:
const keyPressHandler = (e) => {
if (e.key=== 'Enter') {
// alert("You pressed enter!");
history.push("/process/" + myValue);
}
};
Working CodeSandbox Link

Why does cursor move to start of input field when value is changed dynamically?

I have an input field in my react application like below. suggestedTerm and searchTerm are coming from component's state. searchTerm state is being set in onChange handle. suggestedTerm state is being set when i navigate up or down in autocomplete suggestion list.
<input value={suggestedTerm || searchTerm}
onChange={handleInputChange}
onFocus={() => {
setShowFlyout(true);
clearActiveSuggestion();
}}
onKeyDown={handleInputKeyDown}
ref={searchInput}
/>
Here through onKeyDown handler, i am handling up and down arrow key events to navigate through the autosuggestion suggestions list that is being produced while keeping the focus on the input field. That was done basically to cater accesibility.
The requirement is to set the selected suggestion on the input field as we navigate through the autosuggestion list. However, the issue i am facing here is that my cursor moves to the beginning of the input field whenever i set suggestedTerm state which in return sets the input field's value while navigating up using up arrow key. This does not happen when i navigate through down key.
Attaching here my up key and down key logic
if ((e.key === "ArrowUp" || e.keyCode === 38) && !isEmpty(suggestions)) {
const focusedItem = getActiveSuggestion();
const index = focusedItem[0].index - 1;
if (!isEmpty(focusedItem) && focusedItem[0] && focusedItem[0].index > 0) {
setActiveSuggestion(index);
} else {
//Clear active suggestions if up is pressed while focus is on first element
clearActiveSuggestion();
}
if ((e.key === "ArrowDown" || e.keyCode === 40) && !isEmpty(suggestions)) {
const focusedItem = getActiveSuggestion();
//Set first suggestion active
if (isEmpty(focusedItem)) setActiveSuggestion(0);
else {
if (
!isEmpty(focusedItem) &&
focusedItem[0] &&
focusedItem[0].index < (suggestions && suggestions.length - 1)
) {
setActiveSuggestion(focusedItem[0].index + 1);
} else {
//Set first suggestion active when focus is on last item already and down key is pressed
setActiveSuggestion(0);
}
}
}
const clearActiveSuggestion = () => {
setSuggestions(suggestions.map(suggestion => ({ ...suggestion, active: false })));
setSuggestedTerm("");};
const getActiveSuggestion = () => {
return suggestions.filter(suggestion => suggestion.active);};
const setActiveSuggestion = activeItemIndex => {
const updatedSuggestion = suggestions.map(suggestion => {
if(activeItemIndex === suggestion.index)
setSuggestedTerm(suggestion.dq);
return {...suggestion, active: activeItemIndex === suggestion.index};
});
setSuggestions(updatedSuggestion);};
First, i do not understand why my input field is setting cursor to start of the input field even though i am changing the state and state change should reset value of input field.
Second, i searched a number of ways to manually set the cursor using setSelection method and manually setting input field's value using ref but nothing is changing the behavior.
Can anybody figure out the issue here?
Thanks
Figured out the root cause. Actually, it is a default behavior of an input field to take cursor to start of the string in an input field when up arrow is pressed on it so i simply added
e.preventDefault();
and that prevented the default behavior of the input field to move the cursor. It had nothing to do with how i am setting the state or setting the selectionRange of the input manually.

How to stop an event from being further processed in React

I have a text input in my React app which I don't want to take inputs which are greater than 100. For example, If the entered value is 105, an alert is created and the event is terminated i.e changing input value is not gonna happen. Now I couldn't find a way to do this inside onChange function. Any help would be highly appreciated.
<input onChange={handleChange} name="t"/>
handleChange = e => {
if(e.target.value > 100){
alert("High")
//Here I want to stop event so that changing text in the input doesn't happen
}
}
Make it a controlled input and only set the value if a condition is met.
const App = () => {
const [value, setValue] = React.useState("");
const handler = (e) => {
const value = Number(e.target.value);
value <= 100 && setValue(value);
};
return (
<input onInput={handler} type="number" value={value} />
);
}
ReactDOM.render(<App/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If I'm understanding you properly, if a specific condition is not met, you want to prevent the input from reflecting the text the user just entered.
In order to accomplish this, you'll need to control your input's value via state.
That means doing something like this:
<input onChange={handleChange} name="t" value={this.state.tInput}/>
handleChange = e => {
if(parseInt(e.target.value) > 100) {
alert("High")
// set input state here to previous value to re-render and remove unwanted input values from input
return this.setState(({tInput}) => ({tInput}))
}
return this.setState({tInput: e.target.value})
}
handleChange = e => {
if(e.target.value > 100){
alert("High");
e.target.value = "";
}
else {
// your normal processing
}
}
should work.
Explanation:
Your code would simply not be executed in case the if condition is true.
The line e.target.value = "" doesn't actually "not show" the users input (as asked for in comment), but rather overrides it with empty string.
Mention:
This solution has nothing to do with React, but rather works in any context.

Categories