Battling page refresh and building a JavaScript Single Page Application.

When e.preventDefault() isn't working, and other sleepless nights.

Leslie G.

--

Javascript for me was something that I briefly worked with while serving digital ads, or maybe a snippet added into a WYSIWYG site builder when a client didn’t want to see anyone using right-click. (Please don’t ask.)

I had read some tutorials about JavaScript, but it wasn’t until I learned Ruby on Rails that I embarked on my first official JS project, a Single Page Application (SPA) as a Phase IV student of Flatiron School’s Self-Paced Program.

I was inspired to build this project and dedicate it to a wacky but fun college professor, dean, and mentor to thousands of students, Dr. Adrienne O’Brien, who retired after decades of service to New York Institute of Technology. In one of her communications classes, each student was handed a stack of 3x5 flash cards. On each card was a unique new word that we had to research and build a sentence using that word in an instance of mass media critique. Most of my classwork is lost in storage somewhere, but somehow these flash cards survived all household moves and continued to linger around my desk.

I decided to hold off on contacting Dr. O’Brien until I had the finished project, but sadly she passed away just 2 weeks later at the age of 86. Stunned, I rewrote the intro of my project ReadMe in her memory.

I wanted my application to serve as a brief sampling of the critical vocabulary used by media critics and influencers. For brevity, the project was launched with 50 seeded vocabulary words. The frontend was built with HTML5, CSS, and JavaScript ES6, communicating with a backend API of Ruby and Rails. All interactions between client and server were handled asynchronously (AJAX), using JSON as the communication format.

Following a tight schedule, I approached this project differently than previous ones, by working through my bootcamp curriculum modules at the same time as building my project. I also watched a record number of video tutorials. In the end, I believe this saved me at least one week of precious time.

That sharp learning curve did lead to a few bumps. For the remainder of this article, I will focus on event listeners and preventing page refresh. As a SPA project requirement, page refresh or blink of any kind is not allowed to occur. The following notes are from my own troubleshooting experience, plus others who were encountering similar issues for their projects.

If your input entry or click event is not being recognized, double check that you actually have a listener for it. If there’s a listener on your form tag, you may need another listener on the input or button.

Check your scope.

TypeError: Cannot read properties of null (reading ‘addEventListener’)

If you see this in your Console, make sure you have access to yourFunction(e) from where you are trying to call it. Perhaps you haven’t instantiated the Class yet when it’s called in the function. Or if it had been instantiated, it wasn’t passed into the function as a parameter.

Your page load-to-script sequence is off, so what you need is not being read or recognized. An event handler before page load will result in a function or that button is not technically “there.” If that’s the case, put your primary calls (or that button) in DOMContentLoaded and double check that the file <script> tags are in index.html.

Some people place JavaScript <script> tags within the <head> tags toward the top of the HTML page, while others put them toward the bottom of the web page just before the closing </body> tag. During the troubleshooting process, others moved <script> tags to the very bottom, just after the closing </body> tag and before the closing </html> end of page.

Buttons can be tricky. Using <button> type or <input> type can be manipulated in different ways. You can try setting your event listener for “click” instead of “submit.” From feedback, some have more luck using a “submit” listener on the form itself, not on the button.

Try different button approaches. There are pros and cons to these:

<button type=submit” class=btn btn-light”>Create</button>

<input type=submit” class=btn btn-light” value=Create”></input>

Looking at the above, using input type may be more friendly for form input. Note “value” is being used as the button label. Whether you use either <button> or <input>, it’s good to grab that button by its .css .class or by its .css #id.

Side note: Sometimes we want to reserve value to pass along something different, like data. An example below quietly passes data through a form, connecting the form’s submission to its ActiveRecord parameter and parent association:

<input type=hidden” class=input-vocabulary_word_id” name=vocabulary_word_id” value=${this.id}”>

In the example above (within my form), when a user submits an example of a sentence for a unique vocabulary word, this hidden field passes the index of sentence “vocabulary_word_id.” The value=${this.id}” is its parent vocabulary word :id since this input form is nested below the parent Vocabulary Word, in my Vocabulary Word class .js file defining that :id.

The event listener itself can also grab a button by id or class:

document.querySelector(‘.input-example’).addEventListener(‘input-example’ …

If you’re grabbing one button or field from many on your page, instead of document.querySelector try using e.target.querySelector:

const userInput = e.target.querySelector(‘.input-example’).value

const vWordId = parseInt(e.target.querySelector(‘.input-vocabulary_word_id’).value)

Take this one step further by double checking with Inspect the Elements on your web page. Use the pointer on your .css elements and make sure you are grabbing them, exactly. For example, my Delete button with the class=delete-btn btn btn-dark” in my form rendered slightly different online as .delete-btn.btn.btn-dark. The latter is what I needed to have in addEventListener.

Stopping page refresh. According to MDN web docs, preventing default tells the user agent that if the event does not get explicitly handled, its default action should not be taken as it normally would be. Using event.preventDefault(); or its shortened e.preventDefault(); is a common method for blocking default click handling or stopping keystrokes from reaching an edit field.

Place the line e.preventDefault(); before your querySelector grab and addEventListener. Because I had many forms on my page, I needed .forEach to cover all form instances:

document.querySelectorAll(“.create-sentence-form”).forEach(form => {

form.addEventListener(“submit”, (e) => {

e.preventDefault();

document.querySelector(‘.input-example’).addEventListener(‘input-example’, (e) => Vocabulary.grabSentence(e))

After successfully rendering sentences, I had to add another e.preventDefault(); to the event listener to delete a Sentence if the user chooses to do so. So following the similar pattern above:

document.querySelectorAll(“.my-sentence”).forEach(form => {

form.addEventListener(“submit”, (e) => {

e.preventDefault();

document.querySelectorAll(‘.delete-btn.btn.btn-dark’).forEach(button => {

button.addEventListener(“submit”, (e) => { e.preventDefault(), Sentence.deleteSentence(e) })

})

What happens when e.preventDefault(); isn’t working? When your page keeps blinking no matter what, you might start peppering e.preventDefault(); just about everywhere out of frustration.

Understand the nature of “submit” or ”click” makes the page want to refresh, and we are trying to override that default.

Using <button type=button” … > prevents the page from refreshing even if there is a Javascript error which can be useful for testing and development, but not good for form validation where submit is needed.

Using <form class=myForm” onsubmit=return false”> will freeze page refresh, but may cause the form to not submit. You shouldn’t need the onsubmit in the HTML since you're handling it in the JS.

Use console.log(); to verify that your click handler is working, and it will log the data when you click the button. Keep an eye on the refresh button in the browser and see if it blinks.

.then(response => response.json())

.then(sentence => {

console.log(sentence);

const sentenceData = sentence.data

After exhausting all of these efforts, my page refresh problem still persisted, albeit intermittently in my project. The underlying cause was briefly mentioned in one of the video tutorials, but I had watched so many videos that I totally forgot about it:

Rendering to .innerHTML += can trigger bug that affects event listeners. I had to take my event listeners for Sentence form and button from function getSentence() in my index.js file, duplicate that code, and also add it to my Sentence.js file to where I first fetched and rendered sentence:

document.querySelector(‘#s-container’).innerHTML += newSentence.renderMySentence()

… underneath that line. That fixed the problem. Upon project assessment, I was reminded of a better approach using .insertAdjacentHTML() instead, because .innerHTML += despite being popular shorthand it can be inefficient because it wipes everything then reapplies it. Learn more about .insertAdjacentHTML() at MDN Web Docs here.

--

--

Leslie G.

Strategic Communications | Media Relations | Multimedia | Web Development // IAPWE. Conversational writing. Learn something new everyday.