How do I reset an input field on 'Enter' keyUp in React? - javascript

I am creating an input that adds to a list of tags rendered below the input. I would like the input to push the tag to the list when 'Enter' is pushed while the user is focused on the input field. The field then resets to blank or "", and the user can add another tag in the same fashion.
The problem I am having is that after the value gets reset to a blank string, any time I try to grab the value of the input an empty string is returned.
Here is what I have so far:
const [listOfOptions, addOption] = React.useState([])
let newOption = () =>{
let addField = document.getElementById('addOption')
let grabText = addField.value;
console.log('Grabtext ='+ grabText)
addOption([...listOfOptions, grabText])
addField.reset();
}
useEffect(() => {
document.getElementById('addOption').addEventListener("keyup", function(event) {
if (event.key === "Enter") {
newOption()
}}
); }, [listOfOptions])

I would avoid directly manipulating the DOM in React. Let React's Virtual DOM handle that.
You can store the value of the form in state and then on submit it resets the state to ''.
Something like this:
const [value, setValue] = useState('');
const handleFormValueChange = (event) => setValue(event.target.value);
const resetFormValue = () => setValue('');
<input type="text" value={value} onChange={(event) => handleFormValueChange(event)} />
<input type="submit" onSubmit={resetFormValue} />

The answer, taking into consideration the earlier suggestions, thanks for the help, would then be the below:
const [value, setValue] = useState('');
const [listOfOptions, addOption] = React.useState([])
const handleFormValueChange = (event) => setValue(event.target.value);
let newOption = (event) => {
if (event.key === "Enter") {
addOption([...listOfOptions, value]);
setValue('');
}
}
<input value={value} onChange={(event) => handleFormValueChange(event)} onKeyUp={(event)=>newOption(event)} id="addOption" />
This solution uses the Enter keydown to submit and reset the value, whilst modifying only React's virtual DOM.

Assuming that addOption is an <input>, <select>, or <textarea>, it should not have reset() method. Modify .value instead
addField.value = "";
Don't forget to detach eventListener on this component dismount.
useEffect(() => {
const element = document.getElementById("addOption");
const listener = event => {
if (event.key === "Enter") {
newOption();
event.preventDefault();
}
};
element.addEventListener("keyup", listener);
return () => element.removeEventListener("keyup", listener);
}, [listOfOptions]);
Also please avoid directly modifying DOM directly to a React rendered element, as it may disrupt React render process.
Instead, use a state library such as this or event library
Edit:
You should avoid manipulating DOM directly as told in this answer by David Caldwell

Related

Preventing the user from deleting in an input field

I want to have a text field (<input> in HTML) that the user can only use to write text, but when deleting the text, it should prevent him from updating the state of the input element.
<input type="text" />
I couldn't come up with a solution except to detect the user key presses on the keyboard, i.e, using the onKeyUp attribute, and watch the user keystrokes until he presses the Backspace character:
const input = document.querySelector('input')
input.addEventListener('keydown', (e) => {
const keyCode = e.keyCode
if (keyCode === 8) console.log("Backspace pressed!")
})
<input type="text" />
However, there's a missing part here, even though the user won't be able to clear the input content by the Backspace key, he can instead use the Del key.
So I would have then to handle the Del key the same way I handle the Backspace key.
Now, there's another problem, which is trying to modify the input content by overwriting the content as follows:
and then, after that, I will have to prevent the user from editing the text by cutting the content (which I have no idea how to do).
So, what are the alternatives?
Please feel free to use HTML, JavaScript, CSS.
If you're using React, I was able to solve this problem simply by checking if the length of the input value is shorter than the one being memorized in the state, then prevent the user from updating the state:
The answer resides between controlling the value prop (or attribute if you wish) and the onChange handler.
const [inputVal, setInputVal] = useState('')
const handleChange = (e) => {
const value = e.target.value
if(value.length < inputVal.length) return // prevent modifications
setInputVal(value)
}
return <input value={inputVal} onChange={handleChange}/>
Now, the question is, how to do the same when using only HTML/JavaScript?
Perhaps something like this? Then it doesn't matter what they press.
const input = document.querySelector('input')
let previousState = input.value;
input.addEventListener('keydown', (e) => {
if ( previousState.length > input.value.length ) input.value = previousState;
previousState = input.value;
})
In the keydown event, put whatever keycodes you want in an array and see if the current keycode is included. Then create a select event, and blur the input when it fires.
const input = document.querySelector('input');
input.addEventListener('keydown', (e) => {
if ([8, 46].includes(e.keyCode)) {
e.preventDefault()
}
})
input.addEventListener('select', (e) => {
e.target.blur()
})
<input type="text" />
#dqhendricks answer is in the right way, but misses some possibilities:
User can hit backspace (reduce length by one) (covered)
User can select a piece of text and hit backspace or other key (covered)
User can select a piece (or all text) and replace by a different string with same length. (Not covered)
So my suggestion is:
You should listen for change event, then compare the previous string, if the new string starts with the previous string, it's ok. Otherwise we revert the change.
In code ir should look like:
const input = document.querySelector('input');
let previousState = input.value; // initial state
input.addEventListener('change', (e) => {
if (!input.value.startsWith(previousState)) {
input.value = previousState; // revert change
}
previousState = input.value;
})
you can check the length of the input's value, if it became shorter replace it with the previous value
let value;
input.addEventListener('change', ()=>{
if(value && input.value.length < value.length) input.value=value;
else value=input.value;
}
You must have some piece of code that sets the value from the barcode scanner. Once that is being set, also do input.dataset.barcodeValue="scanresult".
Then to make it sufficiently difficult for a regular user to ruin the barcode, you need to prevent several things:
pasting
cutting
typing over the barcode value
dragging text into the input
pressing delete or backspace which would mess up the barcode value.
All of this is covered in the input event.
const input = document.querySelector('#bc-value');
function setValueFromBarcodeScanner(val) {
input.value = input.dataset.barcodeValue = val;
input.addEventListener('input', (e) => {
const { barcodeValue } = input.dataset;
if (barcodeValue && !input.value.startsWith(barcodeValue)) input.value = barcodeValue;
})
}
<input type="text" id="bc-value" />
<button type="button" onclick="setValueFromBarcodeScanner('433-224-221-456')">Set value from Barcode Scanner</button>
I think this might work for you:
const inputElement = document.getElementById('write-only-input');
let lastValue = '';
inputElement.oninput = function(e) {
if (inputElement.value.startsWith(lastValue)) {
lastValue = inputElement.value;
} else {
inputElement.value = lastValue;
}
}
<input type="text" id="write-only-input" />

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.

React - Can I verify if user pressed a key or not with on change event?

I need to know if I can verify if user pressed a key or not only with onchange event, because I get the text on input with a QR reader, but there also exists the possibility for the user to manually enter the data.
I have this code (example):
_getValue = (event) => {
let value = event.target.value;
}
render() {
<input type="text" onChange={this._getValue} />
}
So, on _getValue method, which is from an onchange event, I need to check if the change is coming from a key or from the QR reader.
Thank you to all!
You could use the keydown event.
You would probably end up with something like this
_getValue = (event) => {
let value = event.target.value;
}
const handleKeyPress = (e) => {
e.preventDefault();
return;
}
render() {
<input type="text" onChange={this._getValue} onKeyPress={handleKeyPress} />
}
You can read more on the event on MDN

How can I get focused element and change state in React component?

My intention is to update the 'isEditorFocused' state whenever the focused element changed, and if the div contains the focused element, deliver true into the Editor component.
However, the code does not work as my intention... It updates state only the first two times.
This is my Code. Actually not the exact code, but it is the core part of my question. If there is any typo, please ignore it. I checked it all in my real code file.
export default AddArticle = () => {
const [isEditorFocused, setIsEditorFocused] = React.useState(false);
const editorRef = React.useRef(null);
React.useEffect(() => {
if(editorRef.current !== null) {
if(editorRef.current.contains(document.activeElement)
setIsEditorFocused(true);
else
setIsEditorFocused(false);
}
}, [document.activeElement]}
return (
<div ref={editorRef} tabIndex="0">
<Editor isEditorFocused={isEditorFocused}></Editor>
<FileUploader {some props}/>
</div>
)
}
Not to completely change your code, but couldn't you just use onFocus and onBlur handlers?
For example:
const AddArticle = () => {
const [isEditorFocused, setIsEditorFocused] = React.useState(false);
return (
<div
onFocus={() => {
setIsEditorFocused(true);
}}
onBlur={() => {
setIsEditorFocused(false);
}}
tabIndex="0"
>
<Editor isEditorFocused={isEditorFocused}></Editor>
</div>
);
};
Working codepen
As T J mentions so eloquently, your issue is with document.activeElement
Note regarding React's current support for onFocus vs onFocusIn:
React uses onFocus and onBlur instead of onFocusIn and onFocusOut. All React events are normalized to bubble, so onFocusIn and onFocusOut are not needed/supported by React.
Source: React Github
The main problem is this: [document.activeElement].
The useEffect dependency array only works with React state, and document.activeElement is not React state.
You can try using a focusin event listener on your <div>, if it receives the event it means itself or something inside it got focus, since focusin event bubbles as long as nothing inside is explicitly preventing propagation of this event.
try this way.
const AddArticle = () => {
const [isEditorFocused, setIsEditorFocused] = React.useState(false);
const handleBlur = (e) => {
setIsEditorFocused(false)
};
handleFocus = (){
const currentTarget = e.currentTarget;
if (!currentTarget.contains(document.activeElement)) {
setIsEditorFocused(true);
}
}
return (
<div onBlur={handleBlur} onFocus={handleFocus}>
<Editor isEditorFocused={isEditorFocused}></Editor>
</div>
);
};

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