Reputation: 3535
I'm using a Bootstrap 5.3 modal as a preloader when a user submits a form in my multi‑page application. The modal shows while waiting for the server response and then the user is taken to a new page. However, when the user clicks the browser’s back button to return to the form page, the modal is still visible—even though it should be hidden.
The modal has the fade
class, so I assume there may be a race condition in play here.
I've tried several approaches (including listening for popstate
and pageshow
events) and handling potential race conditions with the modal’s shown.bs.modal
event.
I have seen a few similar posts on this subject, but they are either for older versions of Bootstrap, rely on jQuery, or involve disabling the first page's cache.
Example Code:
// ... form validation code here
// Define the modal element
const modalEl = document.getElementById( "processingModal" );
// Create a new modal instance in Bootstrap
const modalInstance = new bootstrap.Modal( this.modalEl, {
backdrop: 'static',
focus: true,
keyboard: false
} );
// show the modal, letting the user know the form is processing
modalInstance.show();
// ... submit the form
Upvotes: 0
Views: 33
Reputation: 3535
After a lot of experimentation, I found that handling Bootstrap 5.3 modals in multi‑page apps requires some extra care. The key issues were:
BFCache and Navigation: When a user navigates back, the page can be restored from the BFCache. In these cases, events like popstate
might not fire, but pageshow
does (with event.persisted
set to true
). This allows you to detect that the page was restored.
Race Conditions: There was a race condition where the modal’s shown.bs.modal
event fired after I requested a hide()
. I solved this by using a flag (shouldBeHidden
) and a one‑time listener on shown.bs.modal
so that if the modal finishes showing while a hide was requested, it is immediately hidden.
Fade Animation: I temporarily remove the fade class in the force‑hide method to ensure that any ongoing animation doesn’t interfere with closing the modal. In this example, I want the modal to fade-in, but not fade out.
I hope this helps anyone facing similar issues with Bootstrap 5 modals in a multi‑page application! If you have a more elegant or reliable solution, please share so we can all learn from one another.
Tested this solution on: Windows: Chrome, Firefox, Android: Chrome, Firefox IOS: Safari
// PreloaderModal.js (JS Module)
export default class PreloaderModal {
/**
* Initialize the modal.
*/
constructor() {
// Define the modal element
this.modalEl = document.getElementById( "processingModal" );
// Create a new modal instance in Bootstrap
this.modalInstance = new bootstrap.Modal( this.modalEl, {
backdrop: 'static',
focus: true,
keyboard: false
} );
// This flag to determine if the modal should be hidden, defaults to no.
this.shouldBeHidden = false;
// Bind event handlers to ensure proper context
this.onPageShow = this.onPageShow.bind( this );
}
/**
* Show the modal.
*/
show() {
// Reset the flag
this.shouldBeHidden = false;
// add fade from the classlist, just in case it was removed
this.modalEl.classList.add( 'fade' );
// Show the modal.
this.modalInstance.show();
// Add event listeners.
window.addEventListener( 'pageshow', this.onPageShow );
// attach a one-time listener to the modal for the shown event.
// there's a possibility of a race condition where the modal is shown but should be hidden
// if that happens, we hide it immediately.
this.modalEl.addEventListener('shown.bs.modal', () => {
if ( this.shouldBeHidden ) {
this._forceHide();
}
}, { once: true });
}
/**
* Hide the modal.
*/
hide() {
// Set a flag so that if the shown event fires later, we hide immediately.
this.shouldBeHidden = true;
this._forceHide()
}
/**
* Force-hide the modal.
*/
_forceHide() {
// remove fade from the classlist
this.modalEl.classList.remove( 'fade' );
// Using internal state check if necessary; note _isShown is not public API.
if (this.modalInstance._isShown) {
this.modalInstance.hide();
}
// As a fallback, force-remove the "show" class and backdrop.
this.modalEl.classList.remove('show');
const backdrop = document.querySelector('.modal-backdrop');
if ( backdrop ) {
backdrop.remove();
}
}
/**
* When a pageshow event is detected, check to see if it was persisted (user clicked back/forward button)
* if so, hide the modal
* @param {*} event
*/
onPageShow( event ) {
if ( event.persisted ) {
this.hide();
}
}
}
// Using the JS Module
import PreloaderModal from "./components/PreloaderModal";
( function() {
const preloader = new PreloaderModal();
// ... do form validation etc.
preloader.show();
// ... submit the form
})();
Upvotes: 0