1

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

zkwsk
  • 1,960
  • 4
  • 26
  • 34
  • 1
    It appears this is not possible to do via javascript DOM events per https://stackoverflow.com/questions/32428993/why-doesnt-simulating-a-tab-keypress-move-focus-to-the-next-input-field – devspeter Aug 07 '20 at 23:24
  • Unfortunately you seem to be right. See the note about manually firing events not triggering native browser actions: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent – zkwsk Aug 08 '20 at 22:04

3 Answers3

5

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

zkwsk
  • 1,960
  • 4
  • 26
  • 34
0

Try it this way

new KeyboardEvent("keydown", { keyCode: "Tab", which: 9 })

Here's how I got the which https://keycode.info/

Christopher Francisco
  • 15,672
  • 28
  • 94
  • 206
  • Your `keyCode` is incorrect, it should also be `9`. `key` and `code` should be `'Tab'`. If you use https://keyjs.dev/ instead, you can use a hidden feature to copy the whole `KeyboardEvent` object: Just open DevTools and in the console, you will see all the events being logged and you can expand the one you want to copy. – Danziger Sep 26 '20 at 19:26
0

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/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/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.

Danziger
  • 19,628
  • 4
  • 53
  • 83