primereact: how to avoid handling keydown when a popup item is highlighted? - javascript

Using the Autocomplete component of primereact
Im trying to manage the input when its not included in the suggestions,
the idea is to let the user add a new item to the pool if its not already there.
so far i used the onKeyDown event handler to catch and handle 'Enter' press,
then check if the typed value is in the suggestions, if so pick it, otherwise add it.
the problem is when the user would want to go through the items using arrowDown and then pick one using Enter,
then my onkeyDown handler fires and im not sure how to check if theres any item highlighted at the moment to avoid handling it on my own...
perhaps theres another event handler i should use instead of onKeyDown?
just for providing some code, here's a snippet:
export default function BasicDemo() {
[... skipped]
return (
<div className="card flex justify-content-center">
<AutoComplete
value={value}
suggestions={items}
completeMethod={search}
onChange={(e) => setValue(e.value)}
onKeyDown={(e) => {
console.log(e.key);
if (
e.key === "Enter"
// && no popup item is highlighted ?
) {
// do something with the value typed
console.log(value);
}
// else let it be picked by itself from the popup
}}
/>
</div>
);
}
and here's a sandbox

Related

HeadlessUI React, how to detect currently focused element?

I'm trying to build a funky use case:
I have a HeadlessUI dialog, inside this dialog, there is a HeadlessUI Menu component as a nested child, I want to trigger the menu via a keyboard shortcut
I have managed to trigger the dialog, via:
// I took the useWindowEvent from the headlessUI code
useWindowEvent("keydown", (event) => {
if (event.key === "p") {
event.preventDefault()
event.stopPropagation()
selectorRef.current?.click()
}
})
and on my Menu component:
// My react component forwardRefs to the trigger button
export const ProjectSelector = forwardRef(
... some other component code
<Menu.Button ref={ref} as="div">
... other code
This works fine to trigger the Menu, but the menu also has a search bar, so whenever I press the p key, the listener triggers again and programmatically closes the Menu
I have taken a look inside the headlessUI code trying to understand the focus trap code, but it's above my head, is there any way to detect if the parent Dialog has focus? that way I can just ignore the keypress if focus is on the menu :)
Workaround:
While writing the question I stumbled upon a workaround... it is not super pretty, so maybe someone can come up with a better solution:
// I'm pretty sure I'm doing everything wrong in here
// but I couldn't find another way to remotely trigger and keep focus on the menu component
useWindowEvent("keydown", (event) => {
if (
event.key === "p" &&
document.activeElement?.getAttribute("role") !== "menu" &&
document.activeElement?.tagName !== "INPUT"
) {
event.preventDefault()
event.stopPropagation()
selectorRef.current?.click()
}
})

How do I call on a function by either clicking the button or pressing enter?

I've been learning ReactJS and made an app using Web Dev Simplified's video. As a next step I would like to add a keyUp or event listener (whichever works best) so the enter key can be pressed to Add Todo OR by pressing the add button.
Here is the function that adds the input to the list.
function handleAddTodo(e) {
const name = todoNameRef.current.value
if (name === '') return
setTodos(prevTodos => {
return [...prevTodos, { id: uuidv4(), name: name, complete: false}]
})
todoNameRef.current.value = null}
As I was looking for a solution, I found that hooks might do the trick, but I am not sure where or how to add one.
<button class="add" onClick={handleAddTodo}>Add ToDo</button>
And this is the button itself. I thought about adding onKeyup next to onClick but I don't know how to specify the enter key - as in I know it's keycode is 13 but I don't know how to relate that to the function.
What you are trying to do is have a submit action.
Simply add a html form of type submit with action arround you input field and you are good to go.
Also change the button to type submit.
On pressing enter the form would be posted back.
Take a look here. https://reactjs.org/docs/forms.html

How to use Javascript to determine where a user is typing on webpage and react accordingly

I am new to javascript, and am writing a simple bookmarklet for a webpage that has a text input section. Basically what I need is a way to execute the following-
if ( ! (text_section).onkeydown ) {
do whatever
}
I need to ignore key-events when the user is typing inside an input field but otherwise trigger key-events for the entire page whenever the user presses a key. I do know that onkeydown is an event listener/handler I'm just trying to explain what I need to do more accurately.
Use an id for your text-section, for example "myTextSection". Apply the addEventListener to the whole document. If the target is anything else, than the textfield, do whatever:
document.addEventListener("keydown", (event) => {
if(event.target !== document.getElementById("myTextSection")){
//do whatever;
}
});
Note that this behaviour might be irritating for users, that navigate by keyboard (for example tab and space keys) through your page. So you might want to add another if statement checking whether the event.keyCode is alphanumeric. In this case the keyCode is in the ranges
[47-58] (for 0-9),
[64-91] (for A-Z) or
[96-123](for a-z)
You should add an event listener to the target element:
targetElement.addEventListener('keydown', (e) => {
// do something
});
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
It is also possible to use onkeydown property:
targetElement.onkeydown = (e) => {
// do something
}
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onkeydown

React: onClick on Mobile (iPhone) requires 2 taps?

Video demonstrating issue
I have a bunch of clickable components that, when clicked, adds a "card" to a row. On desktop, it works fine, but on mobile (tested on iPhone, does not seem to be an issue for Android tablet), it requires 2 consecutive taps of the same button to fire the onClick function.
These components also have onMouseEnter/onMouseLeave effects on them, to control a global state, which in turn decides if several components should have additional CSS applied (so I can't make it a simple CSS hover effect).
I believe that the mouse effects are interfering with the click event, but I have no idea how I could fix that. Here is the relevant code for this component:
const CardSource = ({ addCard, note, setHoveredNote, hoveredNote }) => {
return (
<Source
onClick={() => addCard(note)}
onMouseEnter={() => setHoveredNote(note)}
onMouseLeave={() => setHoveredNote(null)}
className={
hoveredNote && hoveredNote.index === note.index ? "highlight" : null
}
>
{note.letters[0]}
</Source>
);
};
Furthermore, once a button has been tapped twice, the hover effect CSS "sticks" to that button, and never moves to another button. This seems to happen on both iPhone and Android tablet. I would love to have this not happen anymore either.
I've created a working demonstration of this issue in a sandbox, which if viewed on mobile you should be able to recreate these issues: https://codesandbox.io/s/mobile-requires-2-taps-i9zri?file=/src/Components/CardSource/CardSource.js
Probably the problem with your code is, the mouse events you're using are non-bubbling. e.g. mouseenter event.
You might want to try with an event bubbling solution using onMouseOver instead of onMouseEnter, and onMouseOut instead of onMouseLeave.
const CardSource = ({ addCard, note, setHoveredNote, hoveredNote }) => {
return (
<Source
onClick={() => addCard(note)}
onMouseOver={() => setHoveredNote(note)}
onMouseOut={() => setHoveredNote(null)}
className={
hoveredNote && hoveredNote.index === note.index ? "highlight" : null
}
>
{note.letters[0]}
</Source>
);
};
Should the above NOT work, you could debug this with event type and performing event handling based on it. e.g.
const CardSource = ({ addCard, note, setHoveredNote, hoveredNote }) => {
const eventHandler = (event) => {
const { type, bubbles } = event;
switch(type) {
case "mouseover":
case "mouseenter":
setHoveredNote(note);
break;
case "mouseout":
case "mouseleave":
setHoveredNote(null);
case "click":
addCard(note);
if (bubbles) { // handle hover state
setHoveredNote(note);
}
break;
default:
break;
}
}
const onClick = (event) => eventHandler(event);
const onMouseOver = (event) => eventHandler(event);
const onMouseOut = (event) => eventHandler(event);
return (
<Source
onClick={onClick}
onMouseOver={onMouseOver}
onMouseOut={onMouseOut}
className={
hoveredNote && hoveredNote.index === note.index ? "highlight" : null
}
>
{note.letters[0]}
</Source>
);
};
Also note that, providing arrow functions as props creates new instance of the function on every render. So better use bind in that case or just function references that capture the arguments.
I think I've found the problem when I use an onClick & onMouseEnter & onMouseLeave then test in the browser in mobile mode the onMouseEnter and onClick event fire with the first onClick, you can add a console log to all your events and see the same behavior. The CSS style is staying because the DOM thinks that your element still has the hover attribute. If you click off of the element in question, you will see your onMouseLeave event fire, your css will reset but the element will require two clicks again. I'm not sure what the solution is, or if its even a problem testing on an actual mobile device.
EDIT: A solution I found, is only using the onMouseEnter & onMouseLeave event, since this event fires onClick for mobile and I only want the hover effect on desktop the outcome is what I was after.
EDIT EDIT: To maintain accessibility with the keyboard I added an onKeyDown event to open/close the dropdown button (which is what I was working on)
onKeyDown={(event) => {
if (event.keyCode == 13) {
setShowChildren(showChildren === "hide" ? "show" : "hide");
}
}}
You could just use css hover rather than adding a class via onMouseEnter event, It fixes the two taps issue.
Link to sandbox
If you were to programatically use trigger for hover. You could solve the two clicks issue by using onTouchEnd event (commented in sandbox).
Hope that helps.
I remember having a similar problem. The issue was for me that the state of component does not change immediately, but only upon execution of the render() method. I believe you might have the same issue with asynchronicity for both effects you are describing.
The only render() call I see found in your code is in App.test.js, I usually place that in the respective component.tsx.
References
Submit button takes 2 clicks to call function in React
React.js events need 2 clicks to execute
Use events onTouchStart, onTouchMove, onTouchEnd for calculate touch.

Stop click on child calling method on parent - React/JavaScript

I have a modal in React. When you click the background of the modal, the modal should close. The way I have it set up right now, if you click inside* the modal, it closes as well. Because the modal is inside the background>
handleClose(e) {
e.stopPropagation();
this.props.history.push('/business/dashboard')
}
render() {
return (
<Background onClick={e => this.handleClose(e)} name="BACKGROUND">
<Container onClick={console.log("IT CLICKED")} to={'/business/dashboard'} name="CONTAINER">
....
When I click on Container, the onClick event for Background gets called. I don't want this to happen. This is a form that users will be clicking on all the time. I need the modal to only close when you click outside the modal on Background.
I think it will work if you use stopPropagation on the Container click event instead of the Background. Just make sure that you use the onClick prop in your Container component.
class App extends React.Component {
handleClose = (e) => {
e.stopPropagation();
this.props.history.push("/business/dashboard");
};
render() {
return (
<Background onClick={this.handleClose} name="BACKGROUND">
<Container
onClick={e => e.stopPropagation()}
to={"/business/dashboard"}
name="CONTAINER"
/>
</Background>
);
}
}
EDIT: On rereading the question, the other answer is a simpler solution in this case.
The behavior you want to achieve is generally referred to as an "outside click" handler. There are a couple of decent libraries to handle this [0] and their source is pretty short and readable if you want to know how it works in detail. [1]
The general idea is to register a click event handler on the document in a HOC and check whether the event.target originates inside a React ref via Element.contains browser functionality. If is is, the handler will not be executed.
[0] https://github.com/tj/react-click-outside
[1] https://github.com/tj/react-click-outside/blob/master/index.js

Categories