Reputation: 2126
I have a component with an input element and I would like to trigger a "tab" keypress event on that input element in order to jump to the next input element whenever a certain set of logic happens.
I have a ref on the first input element and I'm attempting to trigger the keypress event like so:
useEffect(() => {
if (ref.current) {
ref.current.focus();
setTimeout(() => {
ref.current.dispatchEvent(
new KeyboardEvent("keypress", { key: "Tab" })
);
}, 3000);
}
});
First I make sure to select the first input element with .focus()
, then after 3 seconds I trigger pressing the tab key, expecting to see the focus move to the next field, but it does not seem to work.
It may seem like a strange example, but this is just a prototype. What I am actually planning to do is to trigger some code when I submit the first input field, that will fetch some rows with additional input fields and once that has been rendered I need to trigger the "tab" key. I would like to avoid attaching refs to these dynamically loaded input fields as I feel it adds a lot of overhead keeping track of the refs and passing them down, when all I need is to leverage the tab order and simulate a keypress to tab to the first dynamic loaded item once it has loaded. I'm ok with adding a single ref to the field you need to submit to populate the dynamic fields.
I noticed a few examples online calling .dispatchEvent()
directly on the ref object, but if I try that I get an error telling that the function does not exist, so I call it on the current
prop instead. Not sure if that has any relevance.
Here is a link to a prototype where the above code was taken from: https://codesandbox.io/s/wizardly-hopper-vrh4w?file=/src/App.js:149-441
Upvotes: 3
Views: 12603
Reputation: 21161
As pointed out by @funkylaundry, you can't trigger native browser actions by dispatching your own events for security reasons.
However, what you want to do can be achieved with the autofocus
(HTML) / autoFocus
(JSX) attribute, as shown below:
const App = () => {
const [showExtraFields, setShowExtraFields] = React.useState(false);
const [showMoreExtraFields, setShowMoreExtraFields] = React.useState(false);
React.useEffect((e) => {
setTimeout(() => {
setShowExtraFields(true);
}, 3000);
setTimeout(() => {
setShowExtraFields(false);
setShowMoreExtraFields(true);
}, 6000);
setTimeout(() => {
setShowExtraFields(true);
setShowMoreExtraFields(true);
}, 9000);
}, []);
return (
<form>
<input placeholder="Input 1" autoFocus />
{ showExtraFields && (<React.Fragment>
<input placeholder="Input 2" autoFocus />
<input placeholder="Input 3" />
<input placeholder="Input 4" />
</React.Fragment>) }
{ showMoreExtraFields && (<React.Fragment>
<input placeholder="Input 5" autoFocus />
<input placeholder="Input 6" />
<input placeholder="Input 7" />
</React.Fragment>) }
</form>
);
}
ReactDOM.render(<App />, document.querySelector('#app'));
body {
font-family: monospace;
}
input {
display: block;
margin-bottom: 8px;
font-family: monospace;
padding: 8px;
background: white;
border: 2px solid black;
}
<script src="https://unpkg.com/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id="app"></div>
Note how the focus goes Input 1 -> Input 2 -> Input 5 -> Input 2
.
Basically, if there's more than one input with autoFocus
on the page, the last element to be mounted gets it. That's why in the last update, when both groups of inputs are shown, the focus goes to Input 2
, which has just been mounted, rather than Input 5
, which comes after Input 2
but was already present on the page.
Upvotes: 0
Reputation: 2126
Per the answer I got in the comments it appears that you cannot trigger native browser behaviour (as a security feature) by emulating keystrokes (there's a note about 2/3 down the page): https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent
Upvotes: 5
Reputation: 16278
Try it this way
new KeyboardEvent("keydown", { keyCode: "Tab", which: 9 })
Here's how I got the which
https://keycode.info/
Upvotes: 0