themesberg / flowbite

Open-source UI component library and front-end development framework based on Tailwind CSS
https://flowbite.com
MIT License
7.95k stars 745 forks source link

Dropdowns don't work in React if rendered after page load #86

Closed sazzer closed 2 years ago

sazzer commented 2 years ago

Describe the bug When using Flowbite with React, if the page updates after first load to include a dropdown that wasn't originally present then the dropdown doesn't open when clicked.

I assume this applies to all JavaScript but I've not tried any others yet.

There are no console errors and nothing obvious at all in any logs. It just only ever works if the Dropdown HTML was present in the first render, and doesn't work if it was only present after an update.

My assumption is that the event listeners are bound to the HTML as it is when the JavaScript first runs, and doesn't use any mechanism to bind to changes that happen later.

Desktop (please complete the following information):

sazzer commented 2 years ago

In fact, I can see from a quick look that the JavaScript is using DOMContentLoaded and document.querySelectorAll, so it is exactly binding to only the HTML that was present after the DOM first loaded.

Which means that any time the page is updated after first render - e.g. in my case when I get an API response - then the Flowbite JavaScript has already run and won't see it.

I believe the correct way to handle this is with MutationObservers, but I'm not entirely sure how well that works.

voitsekhovskymax commented 2 years ago

Is there a solution?

Captp2 commented 2 years ago

I seem to have the same issue with tooltip components. I have a page with some tabs. The tabs name have tooltip which always work Inside the tabs, the tooltip work if i refresh the page, but if i got to another tab and come back, it won't work (but the tooltips of the tab menu themselves do work)

brianshimkus commented 2 years ago

@zoltanszogyenyi Any update to this issue? It's starting to affect a lot of developers and I'm about to migrate my projects from Flowbite to something that works.

zoltanszogyenyi commented 2 years ago

Hey @brianshimkus,

We're preparing a standalone React component library after Monday.

The problem is that the event listeners aren't updating after the virtual DOM change.

An alternative would be to couple Flowbite with Headless UI or write the functionality yourself.

We expect a launch in the next few months in Q2 and that should make every interactive component fully compatible with React and Next.js.

We're doing our absolute best to keep up with the demand for new technologies.

brianshimkus commented 2 years ago

I just implemented the accordion from Headless UI and it works great.

hubsmoke commented 2 years ago

I just implemented the accordion from Headless UI and it works great.

Hey what was the workaround here?

My situation is that I'm using Tailwind 3.x + React. I have a button with a tooltip, which works, but as soon as the button is removed and later added back and rendered, the tooltip no longer works. I'd be happy to manually call a function to re-initialize the listener, but I don't see this in the docs.

Edit: here's my dirty workaround

let ev = document.createEvent("Event");
ev.initEvent("DOMContentLoaded", true, true);
window.document.dispatchEvent(ev);
hubsmoke commented 2 years ago

This seems to be duplicate of https://github.com/themesberg/flowbite/issues/98

bryankeekk commented 2 years ago

Is there any solution for this?

bryankeekk commented 2 years ago

@hubsmoke Hey bro, may I know how to use the solution posted by you? How it works?

Sleitnick commented 2 years ago

This affects more than just dropdowns. It looks like most if not all of the components use DOMContentLoaded event to trigger these components into existence.

brianshimkus commented 2 years ago

This affects more than just dropdowns. It looks like most if not all of the components use DOMContentLoaded event to trigger these components into existence.

Have you given Headless UI a try? It worked like a charm for me.

zoltanszogyenyi commented 2 years ago

Hey everyone!

We have recently started working on a standalone Flowbite React library on Github and we are getting pretty close to finishing it. We still need some help with a few components and if you'd like, you could fork one of the issues and help us out.

After that, the whole React + Next.js compatibility issue will be solved indefinitely.

desprit commented 2 years ago

I'm having a similar issue using Flowbite in my Vue3 application. Tooltips don't work in case elements are dynamically initialized after the page is rendered.

Another scenario where I'm experiencing this issue is when used with Storybooks. After refreshing the page, tooltip works. But if I switch to a different story (page isn't fully reloaded) tooltip no longer shows up. Screenshot 2022-04-08 at 11 49 41

agkayster commented 2 years ago

Hi @zoltanszogyenyi, I would please like to find out if the standalone Flowbite React is now ready. Kind Regards

zoltanszogyenyi commented 2 years ago

Hey @agkayster,

It's still in WIP, but most of the components are ready:

https://github.com/themesberg/flowbite-react

Prophet199607 commented 2 years ago

add the below code to your component

const evt = new Event("DOMContentLoaded", { bubbles: true, cancelable: false });
document.dispatchEvent(evt);
gplhegde commented 2 years ago

Calling the below in the mounted() hook of the Vue component will work. This is not the clean way as it ends up creating duplicate instances of tooltips if the DOM was present before DOMContentLoaded event is triggered.

const VueComponent = {
   mounted() {
       document.querySelectorAll('[data-tooltip-target]').forEach(function (triggerEl) {
      const targetEl = document.getElementById(triggerEl.getAttribute('data-tooltip-target'));
      const triggerType = triggerEl.getAttribute('data-tooltip-trigger');
      const placement = triggerEl.getAttribute('data-tooltip-placement');
      new Tooltip(targetEl, triggerEl, {
        placement: placement ? placement : 'top',
        triggerType: triggerType ? triggerType : 'hover'
       });
     });
   }
}
yyc13236 commented 1 year ago

add the below code to your component

const evt = new Event("DOMContentLoaded", { bubbles: true, cancelable: false });
document.dispatchEvent(evt);

Do you mind showing how you add this to the component? I've tried to add this in useEffect in the component, but it doesn't seem to work.

ayomirotoye commented 1 year ago

add the below code to your component

const evt = new Event("DOMContentLoaded", { bubbles: true, cancelable: false });
document.dispatchEvent(evt);

Do you mind showing how you add this to the component? I've tried to add this in useEffect in the component, but it doesn't seem to work.

i am having same issue

hrauvc commented 1 year ago

Lo que hice en Vue fue crear una directiva en la raíz de la configuración de Vue. De esta manera, cada vez que se renderiza un elemento y ese elemento implementa la directiva, se reinicializa Flowbite.

app.directive('flowbite', {
  mounted(el) {
    setTimeout(() => initFlowbite(), 1500);
  },
});

En este código, he creado la directiva 'flowbite' y he definido la función mounted que se ejecutará cuando el elemento con la directiva se monte en el DOM. Dentro de esta función, he utilizado setTimeout para esperar 1500 milisegundos y luego llamar a initFlowbite() para reinicializar Flowbite.

De esta manera, cada vez que se renderice un elemento con la directiva 'flowbite', se ejecutará la función mounted y se reinicializará Flowbite después de 1500 milisegundos.

Y por ejemplo en la parte del componente:

<tr v-for="model in models" :key="model.id" v-flowbite class="border-b dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700">

Esto es para Vue, pero espero les pueda ayudar para React. Saludos

netgfx commented 1 year ago

The standalone react components aren't a real solution because they are extremely hard to customize, not all properties are exposed and not all tailwind classes are accepted (As Flowbite doesn't include the whole of tailwind css but only a smaller part)

abdullahcicekli commented 1 year ago

In index.jsx, remove "import ReactDOM from 'react-dom/client';" and replace it with "import ReactDOM from 'react-dom';". This solved my issue.

Related Stack Overflow answer: https://stackoverflow.com/a/72667405/17476759

sugionosihuahua commented 6 months ago

i'am using vue js but maybe there's someone who will find this useful. you see, iam using "watch" in the App.vue to watch the changes in the element or data, so whenever the data changes it will trigger the function to "recall" the flowbite cdn script, so that all the function can work again.

here is the example : watch :{ dataToken: function(){ let recaptchaScript = document.createElement('script'); recaptchaScript.setAttribute('src', 'https://unpkg.com/flowbite@1.5.1/dist/flowbite.js'); recaptchaScript.setAttribute("id", "script-flowbite"); document.head.appendChild(recaptchaScript); }, }, put the code above to before or after mounted() in the script section.

in this case, i use "watch" to look the changes in the "dataToken" value, it contain the jwt token or something similar, so whenever the user login or logout, the script will re-appeared.

the cons : the script caller element in the head tag will increased infinitely if the user login and logout repeately without reload or re-open their browser. so you can create element remover to remove the previous script before adding the new one. put this code before the "add new element" script : const scriptFlowBite = document.getElementById("script-flowbite"); scriptFlowBite.remove();

so the code will be like this :

watch :{ dataToken: function(){ const scriptFlowBite = document.getElementById("script-flowbite"); scriptFlowBite.remove(); let recaptchaScript = document.createElement('script'); recaptchaScript.setAttribute('src', 'https://unpkg.com/flowbite@1.5.1/dist/flowbite.js'); recaptchaScript.setAttribute("id", "script-flowbite"); document.head.appendChild(recaptchaScript); } },

don't forget to add the id in the first script call to make sure the script can get deleted and added again, in my case i put my first script call in the mounted(){ }. hope this can help someone.

anslara89 commented 5 months ago

Estuve revisando porque tuve el mismo problema porque al dar F5 servia nuevamente los dropdown y encontré una solución para VUE 3, creando un plugin flowbite.js para que cada vez que cambie de ruta se reinicialice:

//main.js

....
import flowbitePlugin from '@/plugins/flowbite';
...
app.use(router)
.use(flowbitePlugin);
app.mount('#app');

//src/plugins/flowbite.js

import { initFlowbite } from 'flowbite';
export default {
  install(app) {
    app.mixin({
      mounted() {
        initFlowbite(); // Inicializa Flowbite en el montaje del componente
      },
      watch: {
        $route() {
          initFlowbite(); // Re-inicializa Flowbite cuando cambia la ruta
        }
      }
    });
  }
};

//Asegurarse make sure - que en el index.html este importado el js

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/flowbite@1.4.5/dist/flowbite.js"></script>
    <title>Title</title>
  </head>
  <body class="bg-gray-50 dark:bg-gray-800">
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
CalElAn commented 4 months ago

Estuve revisando porque tuve el mismo problema porque al dar F5 servia nuevamente los dropdown y encontré una solución para VUE 3, creando un plugin flowbite.js para que cada vez que cambie de ruta se reinicialice:

//main.js

....
import flowbitePlugin from '@/plugins/flowbite';
...
app.use(router)
.use(flowbitePlugin);
app.mount('#app');

//src/plugins/flowbite.js

import { initFlowbite } from 'flowbite';
export default {
  install(app) {
    app.mixin({
      mounted() {
        initFlowbite(); // Inicializa Flowbite en el montaje del componente
      },
      watch: {
        $route() {
          initFlowbite(); // Re-inicializa Flowbite cuando cambia la ruta
        }
      }
    });
  }
};

//Asegurarse make sure - que en el index.html este importado el js

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/flowbite@1.4.5/dist/flowbite.js"></script>
    <title>Title</title>
  </head>
  <body class="bg-gray-50 dark:bg-gray-800">
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

Wow this solved it for me. The tooltip was not appearing for elements that were dynamically initialized after the page is rendered. Adding the initFlowbite() call to onMounted hook of the dynamically rendered elements solved it for me. Thanks