How to disable hotkeys when inputting? - javascript

When the user presses a certain key, a component shows. I have four such components. When the user is typing or editing, I want to disable the hotkeys.
I have this code in each of the four components
componentDidMount(){
document.body.addEventListener("keypress", (e) => {
if (e.key === "t") { // "n" "w" "," for the others
this.setState({opened: !this.state.opened});
}
});
}
I only want to disable hotkeys when the user is typing or editing. Is there a way to know if any input is in focus? Or the other way, can we add the event listeners only if all the inputs are 'on blur'?

So we need to know if any of the inputs on the page are in focus and if any of them is focused then we just will not do anything to show or hide components.
Let's assume that our component has in the state some property which indicates that some input on the page is focused, let's call it isFocus.
So, we need to collect all inputs on the page, iterate over them all and assign to each input the focus and the blur event handlers, so we will be able to know when to change the isFocus property in the state.
First of all, we need to collect all of the inputs on the page, we do it with:
const inputs = document.getElementsByTagName('input').
Iterate over them all and assign the focus and blur event handlers:
for (let input of inputs) {
input.addEventListener('focus', () => this.setState({isFocus: true}));
input.addEventListener('blur', () => this.setState({isFocus: false}));
}
And finally, let's change the condition for the keypress event:
document.addEventListener('keypress', e => {
if (!this.state.isFocus && e.key === "t") {
this.setState({opened: !this.state.opened});
}
});
Everything together will look like this:
componentDidMount() {
const inputs = document.getElementsByTagName('input');
for (let input of inputs) {
input.addEventListener('focus', () => this.setState({isFocus: true}));
input.addEventListener('blur', () => this.setState({isFocus: false}));
}
document.addEventListener('keypress', e => {
if (!this.state.isFocus && e.key === "t") {
this.setState({opened: !this.state.opened});
}
});
}
Hope this helps. Good luck.

You could move the current open component state to the most upward component, like the following:
state: {
openComponent: null
}
your hotkey function would look like this:
hotkey(e){
const componentKeyHandlers = {
"n": {openComponent: "componentN"},
"m": {openComponent: "componentM"}
}
if (e.keyCode === 27) { //ESC
this.setState({openComponent: null});
} else if (!this.state.openComponent) {
this.setState(componentKeyHandlers[e.key]);
}
}
I'm assuming you could only have ONE open component each time. Also, you could close them by hitting ESC.
For each component, its visibility would be controlled by comparing props.openComponent to its name, given that the current state component is passed down to each one via props.
This way you don't need to unregister the hotkey function. When you start typing with an open component, the setState is going to be ignored due to the if (!this.state.openComponent) condition.

Related

React if else statement in component runs 2 scenarios when I only want 1 to run

I am making a simple drop down menu app.
My goal is to update the look of the menu through DOM manipulation using an if else statement for specific keyboard key clicks.
Here is the code in question along with an explanation of what is happening currently as well as what I want the code to actually do.
componentDidMount() {
window.addEventListener('keydown', e => {
// This listens for a key press
if (e.key === 'Enter') {
if (this.state.menuIsOpen === false) {
menuOptions[0].style.backgroundColor = 'cornflowerblue'
this.setState(() => ({
menuIsOpen: true,
height: '350px'
}));
// ^ This opens the menu and changes a state property.
} else if (this.state.menuIsOpen === true) {
// This does the opposite.
h1.innerHTML = menuOptions[count].innerHTML;
menuOptions[count].style.backgroundColor = '#a9c4f5'
this.setState(() => ({
menuIsOpen: false,
height: '50px'
}));
}
}
})
}
I know what the issue is, my problem is how to get around it. Right now what is happening is when the 'Enter' key is clicked, the code checks the state to see if the condition is false. Because it is, the state gets changed to true, which in turn causes the next if else block to run because now the condition is true.
What I want is for 1 block to run on the 'Enter', once if the menu is closed and once is the menu is already opened. Not both simultaneously.
Thank you in advance for the help :)
I believe that it called multiple times because this event fires not only when you hit some key, but also when you hold it. So it is probably fires few times when you hit enter once.
Maybe try to just change the event that you listen to.
Simple fix is to set event to keyup, so you know for sure that it would be fired only once.
Also if I would have a whole picture, we would can to find out some solution by changing the UX (I believe that you might not want close any menu by hitting enter, so solution might be just to make menu close handler on esc button for example)
I figured it out!
As someone else stated, the event listener was firing twice (I suspect, correct me if I am wrong). To fix this I simply added 2 lines of code which I have highlighted.
Heres an explanation for the 2 lines:
.preventDefault()
.stopImmediatePropagation()
Thank you to everyone who tried to help :)
componentDidMount() {
window.addEventListener('keyup', e => {
e.preventDefault(); // Added this line
e.stopImmediatePropagation(); // And this one
if (e.key === 'Enter') {
if (this.state.menuIsOpen === false) {
menuOptions[0].style.backgroundColor = 'cornflowerblue'
this.setState(() => ({
menuIsOpen: true,
height: '350px'
}));
} else if (this.state.menuIsOpen === true) {
h1.innerHTML = menuOptions[count].innerHTML;
menuOptions[count].style.backgroundColor = '#a9c4f5'
this.setState(() => ({
menuIsOpen: false,
height: '50px'
}));
}
}
})
}

Can I add event to the button on click of input type=search?

Is there any event available for clicking clear button only appears on html5 search input element?
Here is my attempt. But if i use "search", the event is also triggered by pressing enter.
i.e. if i press enter, console prints out
search
clear
mySearchBar.addEventListener("keydown", (e) => {
if (e.keyCode === ENTER_KEY_CODE) {
console.log("search");
//Display search results
}
});
mySearchBar.addEventListener("search", () => {
console.log("clear");
// Click the cross button of input type=search to Clear the search results and display all
});
};
You can use eventListener 'input' to check if input is empty each time something append to input, then if no value that mean the search input was cleared.
EDIT: Added value testing in keydown event
mySearchBar = document.getElementById('search');
mySearchBar.addEventListener("keydown", (e) => {
if (e.key === 'Enter') {
if (!e.currentTarget.value)
console.log('cleared');
else
console.log("search");
}
});
mySearchBar.addEventListener('input', (e) => {
if (!e.currentTarget.value)
console.log('cleared');
})
<input type=search id=search>

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.

On mount, check if Enter/Return key is being pressed and use that information in onBlur event

I have a button that can be highlighted with Tab, where pressing Enter when the button is highlighted creates an Input field. That input field is then focused.
The input field has a keyUp event and if the key is Enter/Return, blur the field (and save the info that was put into the field).
The issue:
Tab to select Button
Keydown Enter/Return to create Input field
Input field is now focused
Keyup Enter/Return
Input becomes blurred
What I need is to know if the enter/return key was pressed on mount and stop the Keyup event from firing if that is true.
export default function EditableLabel({
active,
value: valueProp,
variant,
...rest
}) {
const [returnPressedOnMount, setReturnPressedOnMount] = useState(false);
let isReturnPressed = false;
// Only set this listener on mount
useEffect(() => {
document.addEventListener('keypress', isReturnKey, false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function isReturnKey(event) {
console.log(event.keyCode);
if (!returnPressedOnMount && event.keyCode === 13) {
console.log('In the return key func');
setReturnPressedOnMount(true);
isReturnPressed=true;
console.log(isReturnPressed)
}
document.removeEventListener('keypress', isReturnKey, false);
}
function handleBlur(evt) {
setIsEditing(false);
rest.onChange && rest.onChange({ target: { value } });
rest.onBlur && rest.onBlur(evt);
}
function handleKeyUp(evt) {
console.log(returnPressedOnMount);
console.log(isReturnPressed);
if (evt.key === 'Enter') {
if (!returnPressedOnMount) {
const target = evt.target;
setTimeout(() => target.blur(), 0);
} else {
setReturnPressedOnMount(false);
}
}
rest.onKeyUp && rest.onKeyUp(evt);
}
return (
<Input .../>
);
}
This code works if I hold the Enter/Return key down for a long time, but if someone presses the key normally (just tap the key, not holding it down) the State/Variable do not update in time for the onKeyUp.
How do I get the knowledge of the Return key being pressed on Mount to the onKeyUp function?

React event propagating in the else case

let's say I have a function that gets executed on key press that looks like something like this. I want to have special case for when Enter is pressed otherwise I want even to propogate/bubble up to the browser. Therefore, if any other key is pressed this i.e up or down arrows they should work.
onAutosuggestInputKeyDown = event => {
if (event.key === 'Enter') {
this.onCustomSuggestionCreate(event)
} else {
// keep propgating the event
}
}
getAutosuggestInputProps = () => {
return {
inputProps: {
onBlur: this.onCustomSuggestionCreate,
onKeyDown: this.onAutosuggestInputKeyDown,
},
}
}
<ReactAutoSuggest textFieldProps={this.getAutosuggestInputProps()}/>
If I understand your situation correctly, then even propagation should occour by default (depending on the type of element that fired the event).
You would however, likely want to use stopPropagation() in the case of the enter key being pressed to prevent the propagation of that event, which would be achieved by the following update to your onAutosuggestInputKeyDown method:
onAutosuggestInputKeyDown = event => {
if (event.key === 'Enter') {
// Prevent this event from propagating if enter key pressed
event.stopPropagation()
this.onCustomSuggestionCreate(event)
}
// If stopPropagation() not called on event, the event will propagate
// if it has the ability to do so (ie from the element dispatching the
// event)
}

Categories