Fetch is causing form submission to occur twice - Why?

I noticed that my Fetch (AJAX) implementation was firing the callback twice a while back but it wasn’t a problem until I got my account registration form put in and now submitting the registration form triggers the mysql query twice.

I was going to look into doing something to just prevent double entries but I’d rather fix the cause than patch the symptom.

I’ve narrowed it down to the changeCallback function running twice when I submit the forms but for the life of me I can’t figure why. Does the fetch in the addFetch function mutate the target div twice somehow?

var forms = [];

function changeCallback(mutations)
{
  alert('<h2>In changeCallback</h2>');
  mutations.forEach(function(mutation)
  {
    alert('<h2>In changeCallback and looping mutations</h2>');
    forms = document.getElementsByClassName('fetchForm');
    for (thisForm of forms)
    {
      alert('<h2>In changeCallback and looping forms</h2>');
      addFormListener(thisForm, thisForm.dataset.targetblock)
    }
  });
}

document.addEventListener("DOMContentLoaded", function()
{
  alert('<h2>DOMContentLoaded Called!</h2>');
  const config = {childList: true};
  forms = document.getElementsByClassName('fetchForm');
  for (thisForm of forms)
  {
    alert('<h2>DOMContentLoaded Called and entered form loop</h2>');
    var observer = new MutationObserver(changeCallback);
    var targetBlock = document.getElementById(thisForm.dataset.targetblock);
    observer.observe(targetBlock, config);
    addFormListener(thisForm, thisForm.dataset.targetblock);
  }
});

function addFormListener(form, targetBlock)
{
  alert('<h2>In function addFormListener</h2>');
  form.addEventListener('submit', function()
  {
    addFetch(event, targetBlock);
  });
}

function addFetch(event, targetBlock)
{
  alert('<h2>In function addFetch</h2>');
  event.preventDefault();
  const form = new FormData(event.target);
  const request = new Request(event.target.action,
  {
    method: event.target.method,
    body: form
  })
  fetch(request)
  .then(response => response.text())
  .then(text => document.getElementById(targetBlock).innerHTML = text)
  .catch(error => console.log('There was an error:', error))
}

So if a form with class fetchForm is submitted the server response is inserted into the fetchForms target div and any forms in this new content get the same formatting so that they too will do the same thing. It’s a multi stage admin form handling system but its sending data twice. Where did I mess up?

Also, I’m using the mutation observer instead of a callback in the fetch function because I couldn’t get the addFormListener to apply to forms in the newly inserted html unless I did it after and outside of everything in the addFetch function. No combination of .then would work to add the html and then add the eventlisterner to the inserted html.

Ok, I may have found the cause, but I could use help with the solution.

Right now, in the specific problem case, the document.addEventListener is adding two mutation observers to the same target (Because two separate forms are targeting the same div intentionally). So, now I need to figure out the best method of ensuring the mutation observer is not applied twice to the same target div.

My thought is that I can make a set of target divs in the loop ensuring no duplicates and then iterate through the set assigning the mutation observers? But does a set work with keeping unique DOM Elements? is there a better way?

1 Like

Hi @FolioGraphic, what is the purpose of the mutation observer in the first place? It’s doing the exact same thing you’re already doing inside the DOMContentLoaded callback – adding a submit listener to each .fetchForm. And in case new such forms might get appended to the DOM later, why not add the event listener directly then?

PS: Or alternatively, use event delegation instead so you only need a single event listener at all:

document.addEventListener('submit', function (event) {
  if (event.target.classList.contains('fetchForm')) {
    // ...
  }
})

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.