0

I have a simple form and a field to render the value of that input field from that form

   <div id="app">
      <form id="form">
        <label
          >some form
          <input type="text" name="myInput" />
        </label>
      </form>
      <p id="field"></p>
    </div>

function getFormValues(event) {
  const data = new FormData(event.currentTarget);
  const { myInput } = Object.fromEntries(data);
  console.log("value", myInput);

  field.textContent = myInput;
}

const form = document.getElementById("form");
const field = document.getElementById("field");

form.addEventListener("input", getFormValues); // 

So far it works. However once I debounce it with this util I wrote (btw I am well aware of utils like lodash out there that can do the same thing)

function debounce(fn, delay) {
  let timerId;

  return (...args) => {
    if (timerId) clearTimeout(timerId);
    timerId = setTimeout(() => {
      fn(...args);
    }, delay);
  };
}

const debouncedGetFormValues = debounce(getFormValues, 500);

form.addEventListener("input", debouncedGetFormValues); // Failed to construct 'FormData': parameter 1 is not of type 'HTMLFormElement'.

All of a sudden I got an error saying that Failed to construct 'FormData': parameter 1 is not of type 'HTMLFormElement'.

Here is the live demo https://codesandbox.io/s/awesome-rosalind-1brqi?file=/src/index.js

Not sure what went wrong here.

Please don't vote to close this question just because event currentTarget changes after setTimeout exists. It doesn't solve my problem. Even if you change const data = new FormData(event.currentTarget); to const data = new FormData(event.target);. The problem is still there.

Joji
  • 4,703
  • 7
  • 41
  • 86
  • 1
    That's still the same question yes, you need to understand what `Event.currentTarget` means, and what `Event.target` means. Note that you could in your debouncer add a property that would store the `currentTarget` at the time it fired, e.g `... return (...args) => { if( args[0] instanceof Event ) { args[0].handlingTarget = args[0].currentTarget; } if (timerId) ...` Or maybe even better, just pass the correct `this` to your debounced callback: `return function(...args) { if (timerId) clearTimeout(timerId); timerId = setTimeout(() => { fn.call(this, ...args); }, delay);};`. – Kaiido Apr 08 '21 at 02:36
  • @Kaiido why do you think I don't understand what `currentTarget` and `target` mean? `target` is the element that directly triggers that event. `currentTarget` is the element that the event listener is attached to. What is wrong with using `currentTarget` here? Btw your answer still results in the same error, i.e `fn.call(this, ...args)` doesn't make a difference. – Joji Apr 08 '21 at 15:40
  • Because of what you say, you cleary don't understand how DOM events are propagated and what these properties are. Go read the answers in the dupe, carefully. No element triggers an Event, here the user does, and `.target` is the Element on which this event is dispatched. When dispatching that event it will traverse through all the ancestors of that target (document, body, container etc.), first in the capturing phase, and then in the inverse order in the bubbling phase. The `.currentTarget` represents which of these parents is currently being traversed. – Kaiido Apr 08 '21 at 23:21
  • After a timeout the whole traversal is done and there is no `.currentTarget` anymore. However, instead of using `.currentTarget`, you can still use `this` inside the handler, because the callback is called in the context of the one you attached the handler on. So yes using `.call(this...` works, but you obviously need to use `this` instead of `.currentTarget`. – Kaiido Apr 08 '21 at 23:21
  • @Kaiido thanks for the replies. Got it. I am still having a hard time understanding your solution of using `.call(this,...)`. Not sure what I did wrong here but can you fork my example and tweak my code to show me how to do it? https://codesandbox.io/s/awesome-rosalind-1brqi?file=/src/index.js – Joji Apr 09 '21 at 18:01
  • https://codesandbox.io/s/gracious-mountain-ecq7t commented. – Kaiido Apr 10 '21 at 07:36

1 Answers1

3

The problem comes from the behavior of the input event. The instance of InputEvent comes with the corresponding input element as target, even if you add a listener on the form element.

So, simply rewrite your code as:

function getFormValues(event) {
  const form = event.target.form; // Get the form element
  const data = new FormData(form);
  const { myInput } = Object.fromEntries(data);
  console.log("value", myInput);

  field.textContent = myInput;
}