In my rails 8
app I followed the Bootstrap Getting Started instructions to pull in bootstrap version 5.3.7
via the link
tag:
I put the following in the head
tag of application.html.erb
:
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
I then put the following right before the close of the body
tag of application.html.erb
:
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js" integrity="sha384-7qAoOXltbVP82dhxHAUje59V5r2YsVfBafyUDxEdApLPmcdhBPg1DKg1ERo0BZlK" crossorigin="anonymous"></script>
</body>
Everything works great until I click any links within the app. When I do, I notice various bootstrap components stop working as expected:
navbar-toggler
button (a.k.a hamburger icon) still properly opens the navbar, but it does not close it when clicked again.One thing I do notice when I inspect element on the navbar dropdown within the safari browser: when I click the dropdown, the markup for the ul
of that dropdown briefly flashes (but doesn't change). So it looks like bootstrap is trying to add the show
class to the ul
, and bootstrap is trying to add the data-bs-popper="static"
to the ul
, but something (I'm assuming turbo) is stopping it from happening).
When I click the "Reload this page" icon (that circular arrow icon) within the browser, all the bootstrap components work again as expected.
I'm quite confident that this is some conflict between turbo
and bootstrap
.
What I've tried
Focusing on the dropdown in the navbar, I assumed I needed to re-initialize the dropdown functionality, because perhaps it is stale after clicking the link which is powered by turbo
. I tried adding this to app/javascript/application/js
, but it didn't work:
# app/javascript/application/js
document.addEventListener("turbo:render", () => {
document.querySelectorAll(".dropdown-toggle").forEach((el) => {
if (!el.dataset.bsDropdownInitialized) {
new window.bootstrap.Dropdown(el);
el.dataset.bsDropdownInitialized = "true";
}
});
});
Apparently this conflict is not uncommon between bootstrap and turbo. I know I can always disable turbo, but I'm trying to avoid that if possible.
This kind of behavior usually happens when Bootstrap script is loaded twice, which probably happens because Turbo is replacing all body
child elements with another Bootstrap script placed in the body
, causing conflict, most likely with internal event listeners.
Try moving Bootstrap scripts from the body
tag to the head
tag in application.html.erb
, so that Turbo doesn't replace it (also make sure you're not also bundling Bootstrap or loading it somewhere other than with CDN, i.e. that no multiple Bootstrap scripts are being loaded on the page):
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-LN+7fdVzj6u52u30Kp6M/trliBMCMKTyK833zpbD+pXdCLuTusPj697FH4R/5mcr" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.min.js" integrity="sha384-7qAoOXltbVP82dhxHAUje59V5r2YsVfBafyUDxEdApLPmcdhBPg1DKg1ERo0BZlK" crossorigin="anonymous"></script>
Bootstrap usually works fine with dynamically added elements (dropdown, modal), but if you still might need to reinitiate BS components (especially popovers, tooltips) when the DOM changes, you could use .getOrCreateInstance
static method, which gets the component instance associated with a DOM element, or creates a new one in case it wasn’t initialized, so no need to add instantiated flags and check data attributes or similar:
# app/javascript/application/js
document.addEventListener("turbo:render", () => {
document.querySelectorAll(".dropdown-toggle").forEach((el) => {
window.bootstrap.Dropdown.getOrCreateInstance(el);
});
});
To speed up page load (which is why it says to place them at the bottom of body
), for the scripts in the head
tag you can set defer
attribute, so that the execution of the scripts will be deferred until the DOM has been parsed, and you can use Bootstrap bundle version:
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js" integrity="sha384-ndDqU0Gzau9qJ1lfW4pNLlhNTkCfHzAVBReH9diLvGRem5+R9g2FzA8ZGN954O5Q" crossorigin="anonymous"></script>